37

Yesterday I took part in a discussion on SO devoted to OutOfMemoryException and the pros and cons of handling it (C# try {} catch {}).

My pros for handling it were:

  • The fact that OutOfMemoryException was thrown doesn't generally mean that the state of a program was corrupted;
  • According to documentation "the following Microsoft intermediate (MSIL) instructions throw OutOfMemoryException: box, newarr, newobj" which just (usually) means that the CLR attempted to find a block of memory of a given size and was unable to do that; it does not mean that no single byte left at our disposition;

But not all people were agree with that and speculated about unknown program state after this exception and an inability to do something useful since it will require even more memory.

Therefore my question is: what are the serious reasons not to handle OutOfMemoryException and immediately give up when it occurs?

Edited: Do you think that OOME is as fatal as ExecutionEngineException?

Community
  • 1
  • 1
Igor Korkhov
  • 8,283
  • 1
  • 26
  • 31
  • 4
    What can you do when you catch an `OutOfMemoryException`? – Rubens Farias Jan 22 '10 at 12:29
  • Rubens, I caught an OutOfMemoryException within our code due to a ListView creating copies of the images rather than returning the actual references for custom drawing. I couldn't figure out why, but catching an OOME, calling a GC.Collect() and re-drawing was the only workaround I could find at the time. – Ian Jan 22 '10 at 12:31
  • 5
    @Rubens - It depends on why the exception was caused (you could be trying to allocate a large array yet still have lots of memory available for smaller objects), or the exception could have made objects go out of scope and be eligible for collection (this is a Java perspective, but I'll assume CLR is similar). – kdgregory Jan 22 '10 at 12:32
  • 4
    You can free memory and terminate the running thread... Then inform the user of the failed job. – Cipi Jan 22 '10 at 12:33
  • 3
    @Rubens Farias: Of course, it depends on a situation. Let's imagine MDI bitmap editor a la Photoshop. When user wants to open theirs 101st window and we unable to allocate enough memory, we can clean old undo history, or render the image with less resolution or at least ask user to close several previous windows. – Igor Korkhov Jan 22 '10 at 12:34
  • 5
    @Cipi - IMO the only way to get *enough* isolation to robustly handle an OOM is to use a separate *process*. – Marc Gravell Jan 22 '10 at 12:36
  • +1 for following through with this! – Aaronaught Jan 22 '10 at 14:08
  • 1
    Interesting edit - I don't think that `OutOfMemoryException` is quite as catastrophic as `ExecutionEngineException` - or `StackOverFlowException`, `InvalidProgramException`, `BadImageFormatException`, or `AccessViolationException` for that matter - but it is *very close*. It's probably safe to say that you can *never* truly recover from those, whereas with `OutOfMemoryException` it's just "almost never." – Aaronaught Jan 22 '10 at 15:49
  • It was a really useful discussion! I'd like to thank everyone for their great answers and comments. – Igor Korkhov Jan 25 '10 at 12:00
  • @Marc why a separate process? Separate AppDomain should be enough since we're talking about managed memory here. – CodesInChaos Sep 08 '11 at 13:37
  • @CodeInChaos well, things get... really ugly recovering from an OOM. It is very hard to guarantee that all the cleanups you *should* have performed *were* actually performed, since the AppDomain was in a sickly state. You can never be sure what happened. If integrity and robustness is important, and you need to handle this, I would use a separate process. But YMMV. – Marc Gravell Sep 08 '11 at 13:40

10 Answers10

39

IMO, since you can't predict what you can/can't do after an OOM (so you can't reliably process the error), or what else did/didn't happen when unrolling the stack to where you are (so the BCL hasn't reliably processed the error), your app must now be assumed to be in a corrupt state. If you "fix" your code by handling this exception you are burying your head in the sand.

I could be wrong here, but to me this message says BIG TROUBLE. The correct fix is to figure out why you have chomped though memory, and address that (for example, have you got a leak? could you switch to a streaming API?). Even switching to x64 isn't a magic bullet here; arrays (and hence lists) are still size limited; and the increased reference size means you can fix numerically fewer references in the 2GB object cap.

If you need to chance processing some data, and are happy for it to fail: launch a second process (an AppDomain isn't good enough). If it blows up, tear down the process. Problem solved, and your original process/AppDomain is safe.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    +1, to try identifying what is actually causing memory to be eaten up in the first place - prevention is better than cure and all that – AdaTheDev Jan 22 '10 at 12:46
  • 4
    @Marc Gravell: I think that *sometimes* we can predict what we can do. Imagine the situation when we call 3rd party library method which we cannot control, but we know that it might consume large amount of memory. If we catch OOM then we'll have several options, won't we? Isn't it better than just leave our user with pathetic screen saying "The Application has encountered error and needs to close?" – Igor Korkhov Jan 22 '10 at 14:25
  • @Marc Gravell: What do you think about ChrisF's comment below? – Igor Korkhov Jan 22 '10 at 14:29
  • 1
    If you catch that exception by wrapping a single call to "new Type[large number]", it should be *relatively* safe to assume you are still in an uncorrupted program state. – Lasse V. Karlsen Jan 22 '10 at 14:36
  • Unrolling the stack only occurred on the threads that the exception was caught on. And the stack must be clean before you can enter a try/catch block. Even if you get several OutOfMemoryExceptions on several threads at once... you can pretty safely assume they are in a known state. (If they aren't caught, the process is going down anyway.) – Matthew Whited Jan 22 '10 at 15:41
  • 1
    I don't think that you are necessarily in an corrupted state after an OOM. If you catch this Error around a block you know, that this Error was created because the enclosed code wanted memory that wasn't available. You can assume the operation in question is failed but the program itself is in a proper state and can go on. – Mnementh Jan 25 '10 at 11:27
  • 1
    Despite the fact that `InsufficientMemmoryException` inherits from `OutOfmemoryException` the two exceptions are slightly different. According the the MSDN article for the [former](http://msdn.microsoft.com/en-us/library/system.insufficientmemoryexception.aspx): `Unlike OutOfMemoryException, InsufficientMemoryException is thrown before starting an operation, and thus does not imply state corruption. An application can catch this exception, throttle back its memory usage, and avoid actual out of memory conditions and their potential for corrupting program state.` – Chris Kerekes Aug 29 '12 at 18:28
22

We all write different applications. In a WinForms or ASP.Net app I would probably just log the exception, notify the user, try to save state, and shutdown/restart. But as Igor mentioned in the comments this could very well be from building some form of image editing application and the process of loading the 100th 20MB RAW image could push the app over the edge. Do you really want the use to lose all of their work from something as simple as saying. "Sorry, unable to load more images at this time".

Another common instance that it could be useful to catch out of memory exceptions is in back end batch processing. You could have a standard model of loading multi-mega-byte files into memory for processing, but then one day out of the blue a multi-giga-byte file is loaded. When the out-of-memory occurs you could log the message to a user notification queue and then move on to the next file.

Yes it is possible that something else could blow at the same time, but those too would be logged and notified if possible. If finally the GC is unable to process any more memory the application is going to go down hard anyway. (The GC runs in an unprotected thread.)

Don't forget we all develop different types of applications. And unless you are on older, constrained machines you will probably never get an OutOfMemoryException for typical business apps... but then again not all of us are business tool developers.

To your edit...

Out-of-memory may be caused by unmanaged memory fragmentation and pinning. It can also be caused by large allocation requests. If we were to put up a white flag and draw a line in the sand over such simple issues, nothing would ever get done in large data processing projects. Now comparing that to a fatal Engine exception, well there is nothing you can do at the point the runtime falls over dead under your code. Hopefully you are able to log (but probably not) why your code fell on its face so you can prevent it in the future. But, more importantly, hopefully your code is written in a manner that could allow for safe recovery of as much data as you can. Maybe even recover the last known good state in your application and possibly skip the offending corrupt data and allow it to be manually processed and recovered.

Yet at the same time it is just as possible to have data corruption caused by SQL injection, out-of-sync versions of software, pointer manipulation, buffer over runs, and many other problems. Avoiding an issue just because you think you may not recover from it is a great way to give users error messages as constructive as Please contact your system administrator.

Jeff B
  • 8,572
  • 17
  • 61
  • 140
Matthew Whited
  • 22,160
  • 4
  • 52
  • 69
  • As I pointed out in the original side discussion, batch processing is not a good candidate for this because you could end up with data corruption. I've seen it happen. You think you have the situation under control, but you don't. It took over a year of this code being in production before it ever happened, but when it did, the ensuing havoc was spectacular. – Aaronaught Jan 22 '10 at 14:52
  • 1
    Your right... like anything in programming, if you don't pay attention to the state of your application, you can corrupt data. But that is what we are paid to prevent if we can and control/fix if we must. – Matthew Whited Jan 22 '10 at 15:25
19

Some commenters have noted that there are situations, when OOM could be the immediate result of attempting to allocate a large number of bytes (graphics application, allocating large array, etc.). Note that for that purpose you could use the MemoryFailPoint class, which raises an InsufficientMemoryException (itself derived from OutOfMemoryException). That can be caught safely, as it is raised before the actual attempt to allocate the memory has been made. However, this can only really reduce the likelyness of an OOM, never fully prevent it.

Christian.K
  • 47,778
  • 10
  • 99
  • 143
  • 3
    It should also be noted that the MSDN article for [InsufficientMemoryException](http://msdn.microsoft.com/en-us/library/system.insufficientmemoryexception.aspx) states: `Unlike OutOfMemoryException, InsufficientMemoryException is thrown before starting an operation, and thus does not imply state corruption. An application can catch this exception, throttle back its memory usage, and avoid actual out of memory conditions and their potential for corrupting program state.` – Chris Kerekes Aug 29 '12 at 18:10
13

It all depends on the situation.

Quite a few years ago now I was working on a real-time 3D rendering engine. At the time we loaded all the geometry for the model into memory on start up, but only loaded the texture images when we needed to display them. This meant when the day came our customers were loading huge (2GB) models we were able to cope. The geometry occupied less than 2GB, but when all the textures were added it would be > 2GB. By trapping the out of memory error that was raised when we tried to load the texture we were able to carry on displaying the model, but just as the plain geometry.

We still had a problem if the geometry was > 2GB, but that was a different story.

Obviously, if you get an out of memory error with something fundamental to your application then you've got no choice but to shut down - but do that as gracefully as you can.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
11

Suggest Christopher Brumme's comment in "Framework Design Guideline" p.238 (7.3.7 OutOfMemoryException):

At one end of the spectrum, an OutOfMemoryException could be the result of a failure to obtain 12 bytes for implicitly autoboxing, or a failure to JIT some code that is required for critical backout. These cases are catastrophic failures and ideally would result in termination of the process. At the other end of the spectrum, an OutOfMemoryException could be the result of a thread asking for a 1 GB byte array. The fact that we failed this allocation attempt has no impact on the consistency and viability of the rest of the process.

The sad fact is that CRL 2.0 cannot distinguish among any points on this spectrum. In most managed processes, all OutOfMemoryExceptions are considered equivalent and they all result in a managed exception being propagated up the thread. However, you cannot depend on your backout code being executed, because we might fail to JIT some of your backout methods, or we might fail to execute static constructors required for backout.

Also, keep in mind that all other exceptions can get folded into an OutOfMemoryException if there isn't enough memory to instantiate those other exception objects. Also, we will give you a unique OutOfMemoryException with its own stack trace if we can. But if we are tight enough on memory, you will share an uninteresting global instance with everyone else in the process.

My best recommendation is that you treat OutOfMemoryException like any other application exception. You make your best attempts to handle it and ramain consistent. In the future, I hope the CLR can do a better job of distinguishing catastrophic OOM from the 1 GB byte array case. If so, we might provoke termination of the process for the catastrophic cases, leaving the application to deal with the less risky ones. By threating all OOM cases as the less risky ones, you are preparing for that day.

Sergey Teplyakov
  • 11,477
  • 34
  • 49
  • Very true, if it was a catastrophic exception you could likely continue to get OutOfMemoryExceptions even in that handler. These would bubble to he application handler or just crash the processes. I think you could decide at that point the exception was not one you could recover from. – Matthew Whited Jan 22 '10 at 14:39
10

Marc Gravell has already provided an excellent answer; seeing as how I partly "inspired" this question, I would like to add one thing:

One of the core principles of exception handling is never to throw an exception inside an exception handler. (Note - re-throwing a domain-specific and/or wrapped exception is OK; I am talking about an unexpected exception here.)

There are all sorts of reasons why you need to prevent this from happening:

  • At best, you mask the original exception; it becomes impossible to know for sure where the program originally failed.

  • In some cases, the runtime may simply be unable to handle an unhandled exception in an exception handler (say that 5 times fast). In ASP.NET, for example, installing an exception handler at certain stages of the pipeline and failing in that handler will simply kill the request - or crash the worker process, I forget which.

  • In other cases, you may open yourself up to the possibility of an infinite loop in the exception handler. This may sound like a silly thing to do, but I have seen cases where somebody tries to handle an exception by logging it, and when the logging fails... they try to log the failure. Most of us probably wouldn't deliberately write code like this, but depending on how you structure your program's exception handling, you can end up doing it by accident.

So what does this have to do with OutOfMemoryException specifically?

An OutOfMemoryException doesn't tell you anything about why the memory allocation failed. You might assume that it was because you tried to allocate a huge buffer, but maybe it wasn't. Maybe some other rogue process on the system has literally consumed all of the available address space and you don't have a single byte left. Maybe some other thread in your own program went awry and went into an infinite loop, allocating new memory on each iteration, and that thread has long since failed by the time the OutOfMemoryException ends up on your current stack frame. The point is that you don't actually know just how bad the memory situation is, even if you think you do.

So start thinking about this situation now. Some operation just failed at an unspecified point deep in the bowels of the .NET framework and propagated up an OutOfMemoryException. What meaningful work can you perform in your exception handler that does not involve allocating more memory? Write to a log file? That takes memory. Display an error message? That takes even more memory. Send an alert e-mail? Don't even think about it.

If you try to do these things - and fail - then you'll end up with non-deterministic behaviour. You'll possibly mask the out-of-memory error and get mysterious bug reports with mysterious error messages bubbling up from all kinds of low-level components you wrote that aren't supposed to be able to fail. Fundamentally, you've violated your own program's invariants, and this is going to be a nightmare to debug if your program ever does end up running under low-memory conditions.

One of the arguments presented to me before was that you might catch an OutOfMemoryException and then switch to lower-memory code, like a smaller buffer or a streaming model. However, this "Expection Handling" is a well-known anti-pattern. If you know you're about to chew up a huge amount of memory and aren't sure whether or not the system can handle it, then check the available memory, or better yet, just refactor your code so that it doesn't need so much memory all at once. Don't rely on the OutOfMemoryException to do it for you, because - who knows - maybe the allocation will just barely succeed and trigger a bunch of out-of-memory errors immediately after your exception handler (possibly in some completely different component).

So my simple answer to this question is: Never.

My weasel-answer to this question is: It's OK in a global exception handler, if you're really really careful. Not in a try-catch block.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • The funny thing is that I absolutely agree with the points in your answer above. As far as I can see now the only misunderstanding between us is the interpretation of the term "handling", by which I meant a "catch (OutOfMemoryException e)" block. I didn't want to discuss what should and what shouldn't be inside the block, it is the very presence or absence of it that worried me. And I still don't see anything bad in catching OOME and attempting to deal with it depending on the scenario, even if it would and up just *trying* to say "Good bye" nicely. – Igor Korkhov Jan 22 '10 at 15:30
  • I don't think anyone disagees there are times you can't do anything about the issue other then kill the process. Yet at the same time there are many things you can do. The great thing about the way that .Net works is you are only going to get exceptions for calls on your thread that are inside of your current stack. Even if this is caused by a different thread using all the memory and your local thread doesn't have enough room to create an Int32... Both threads would get an OOM and the application is still in a known state. – Matthew Whited Jan 22 '10 at 15:50
  • 1
    @Matthew Whited: You and I may have different definitions of the term "known state". If you normally depend on one thread to exit a critical section, and your app encounters a fatal error, it may not be a good idea to wait on that critical section as part of your cleanup code and subsequently deadlock the application in addition to your other problems. Again, I will repeat: You may *think* you have things under control, but eventually you will come across a situation that makes you realize you didn't. – Aaronaught Jan 22 '10 at 15:57
  • 1
    To add: It's also not true that an exception can only ever come from your current thread/stack. .NET does do some exception marshaling. – Aaronaught Jan 22 '10 at 15:58
  • I didn't say where the exception occurs. I said where it can be caught. If your thread even caught an unmanaged exception caused by some faulty hardware, you still know the state (assuming that hardware isn't the memory or CPU... but if those explode your app isn't the only one going down.) – Matthew Whited Jan 22 '10 at 16:17
  • @Matthew Whited: You said, and I quote, "The great thing about the way that .Net works is you are only going to get exceptions for calls on your thread that are inside of your current stack." This is false, and even if true would not solve the issue of inter-thread/inter-process dependencies. – Aaronaught Jan 22 '10 at 16:32
  • Okay, sorry... you only get exceptions that affect your current stack. And you are not going to get an exception in a thread from an unrelated process that you are not communicating with. And if you are communicating to it, it will be in your current stack somewhere. You aren’t going to get random exception from some other application running in its own protected memory space. Oh, and feel free to spin up some extra threads and throw exceptions in them. You can even to it across AppDomains and Processes. You will take out those threads but you can easily save the rest of the application. – Matthew Whited Jan 22 '10 at 16:37
  • I'll just say we will probably not agree 100% on this topic we probably write different kinds of software with different requirements. I typically don’t have the option of recycling a process on a whim. – Matthew Whited Jan 22 '10 at 16:44
  • @Matthew Whited: You're wrong, I've written more than a few large "batch processing" type programs that take 10+ hours to run, they can't fail on a whim, and yet I've managed to do it without ever catching a generic `System.Exception` or something like `OutOfMemoryException`. I think you know that there are better methods for hardening software, but don't want to lose the convenience of a generic exception handler. – Aaronaught Jan 22 '10 at 19:58
  • I love personal attacks. Anyway, this isn't a resume contest just a difference of opinion. – Matthew Whited Jan 22 '10 at 20:30
  • @Matthew Whited: I apologize if it came off as a personal attack, but you've been making a lot of assumptions about other people's experience in order to justify your assertion and I thought it was about time to call that out. I'm not ignorant of your requirements; what I've been trying to say from the beginning is that there are more appropriate designs available. – Aaronaught Jan 22 '10 at 20:42
  • I have never had a problem with catching general exceptions. Everything is in the requirements of how you need to process sets of data. And any design that works is the right design. My issue is with making flat assertion that there is only one correct way to cover any given task. – Matthew Whited Jan 22 '10 at 21:22
  • (And my personal attack comment was only targeting the "You’re wrong" part of your statement. You could have got the same point across without it. Also no worries I wasn't offended just felt like toss a jab back.) – Matthew Whited Jan 22 '10 at 21:25
7

One practical reason for catching this exception is to attempt a graceful shutdown, with a friendly error message instead of an exception trace.

Daniel Vassallo
  • 337,827
  • 72
  • 505
  • 443
  • 2
    @Aaronaught: You're absolutely right about it, but why you disagree with my point which is as simple as that: if you try, you'll probably succeed. Or you won't. But if you don't try, you will not succeed for sure. – Igor Korkhov Jan 22 '10 at 14:38
  • 1
    @Igor Korkhov: Because Bad Things can happen if you *don't* succeed. I've outlined this in my own answer. I would consider the (now edited) graceful shutdown a reasonable answer, however, because if the process is shutting down anyway, cleanup code is largely optional (if it fails, then you're no worse off). So +1 to Daniel for the edited answer. :-) – Aaronaught Jan 22 '10 at 14:56
2

The problem is larger than .NET. Almost any application written from the fifties to now has big problems if no memory is available.

With virtual address spaces the problem has been sort-of salvaged but NOT solved because even address spaces of 2GB or 4GB may become too small. There are no commonly available patterns to handle out-of-memory. There could be an out-of-memory warning method, a panic method etc. that is guaranteed to still have memory available.

If you receive an OutOfMemoryException from .NET almost anything may be the case. 2 MB still available, just 100 bytes, whatever. I wouldn't want to catch this exception (except to shutdown without a failure dialog). We need better concepts. Then you may get a MemoryLowException where you CAN react to all sorts of situations.

Thorsten79
  • 10,038
  • 6
  • 38
  • 54
  • (`MemoryLowException`) Ooh, I like that - it may be better to raise as an AppDomain event rather than an exception, though. – Marc Gravell Jan 22 '10 at 12:38
  • @Thorsten79: "I wouldn't want to catch this exception (except to shutdown without a failure dialog)" Even in a situation when your code just wants to grab another 100 meg chunk of memory? You would prefer to give up even in this case? I really do not understand that. – Igor Korkhov Jan 22 '10 at 14:36
  • 1
    If I saw somebody grabbing "another 100 meg chunk of memory", I'd recommend completely rethinking the design. – Thorsten79 Jan 22 '10 at 15:10
  • 1
    If that was all they did... you're probably right. If they were to unload a few unused add-ons or bounce a few unmanaged libraries to reduce memory fragmentation, that's a different issue. (Yes it could fail a second time to which you could do something different, such as kill the process.) – Matthew Whited Jan 22 '10 at 15:52
  • Ok, not 100 meg, 10 meg, or even 1. We all know that we can design every algorithm so that it would be sufficient to look at just one byte of information at any given time, cf. Turing Machine. But do you do that? Or do you know someone who does? :) Having said that I still believe that 3 megs of memory might not be enough to slurp up an image, but might be enough to log that fact and die nicely. – Igor Korkhov Jan 22 '10 at 16:06
1

The problem is that - in contrast to other Exceptions - you usually have a low memory situation when the exception occurs (except when the memory to be allocated was huge, but you don't really know when you catch the exception).

Therefore, you must be very careful not to allocate memory when handling this exception. And while this sounds easy it's not, actually it's very hard to avoid any memory allocation and do something useful. Therefore, catching it is usually not a good idea IMHO.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • ..."usually have a low memory situation" ... that isn't necessarily true. See ChrisF's example http://stackoverflow.com/questions/2117142/when-is-it-ok-to-catch-an-outofmemoryexception-and-how-to-handle-it/2117280#2117280 – Matthew Whited Jan 22 '10 at 14:41
  • I did mention the case with huge allocations. Still, you cannot do a "catch only if memory to be allocated > 1mb" or so, therefore you still have to be careful. – Lucero Jan 22 '10 at 15:12
-2

Write code, don't hijack the JVM. When VM is humbly telling you that a memory allocation request failed your best bet is to discard the state of application to avert corrupting application data. Even if you decide to catch OOM you should only try to gather diagnostic information like dumping log, stacktrace etc. Please do not try to initiate a backout procedure as you are not sure whether it will get a chance to execute or not.

Real world analogy: You are traveling in a plane and all engines fail. What would you do after catching a AllEngineFailureException ? Best bet is to grab the mask and prepare for a crash.

When in OOM, dump!!

Ravi Gupta
  • 4,468
  • 12
  • 54
  • 85
  • 2
    The best thing to do is set the plane on a safe glide slope and then try to restart the engines. Engines failing on a plane is nothing like the wings falling off. – Matthew Whited Sep 15 '17 at 13:43