Catching java.lang.OutOfMemoryError?
Asked Answered
M

14

126

Documentation for java.lang.Error says:

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch

But as java.lang.Error is a subclass of java.lang.Throwable, I can catch this type of Throwable.

I understand why it's not a good idea to catch this sort of exception. As far as I understand, if we decide to catch it, the catch handler should not allocate any memory by itself. Otherwise OutOfMemoryError will be thrown again.

So, my question is:

  1. Are there any real world scenarios when catching java.lang.OutOfMemoryError might be a good idea?
  2. If we decide to catch java.lang.OutOfMemoryError, how can we make sure the catch handler doesn't allocate any memory by itself (any tools or best practices)?
Manlike answered 20/4, 2010 at 23:11 Comment(4)
Similar questions: #1692730 and #353280Southsoutheast
For your first question, I'll add that I will catch the OutOfMemoryError in order to (at least try to) notify the user of the problem. Previously, the error wasn't caught by the catch (Exception e) clause, and no feedback was shown to the user.Muffle
There are specific cases, eg, allocating a gigantic array, where one can catch the OOM error around that operation and recover reasonably well. But placing the try/catch around a large blob of code and attempting to recover cleanly and continue is probably a bad idea.Tong
See also #14377424Hobbledehoy
F
100

There are a number of scenarios where you may wish to catch an OutOfMemoryError and in my experience (on Windows and Solaris JVMs), only very infrequently is OutOfMemoryError the death-knell to a JVM.

There is only one good reason to catch an OutOfMemoryError and that is to close down gracefully, cleanly releasing resources and logging the reason for the failure best you can (if it is still possible to do so).

In general, the OutOfMemoryError occurs due to a block memory allocation that cannot be satisfied with the remaining resources of the heap.

When the Error is thrown the heap contains the same amount of allocated objects as before the unsuccessful allocation and now is the time to drop references to run-time objects to free even more memory that may be required for cleanup. In these cases, it may even be possible to continue but that would definitely be a bad idea as you can never be 100% certain that the JVM is in a reparable state.

Demonstration that OutOfMemoryError does not mean that the JVM is out of memory in the catch block:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" +maxMemory+"M");
        }
    }
}

Output of this code:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

If running something critical, I usually catch the Error, log it to syserr, then log it using my logging framework of choice, then proceed to release resources and close down in a clean fashion. What's the worst that can happen? The JVM is dying (or already dead) anyway and by catching the Error there is at least a chance of cleanup.

The caveat is that you have to target the catching of these types of errors only in places where cleanup is possible. Don't blanket catch(Throwable t) {} everywhere or nonsense like that.

Fokker answered 21/4, 2010 at 6:33 Comment(6)
Agree, I'll post my experiment on a new answer.Repand
"you can never be 100% certain that the JVM is in a reparable state": because the OutOfMemoryError might have been thrown from a point that has placed yout program in an inconsistent state, because it can be thrown at any time. See #8729366Hobbledehoy
In OpenJdk1.7.0_40, I don't get any error or exception when I run this code. Even I changed the MEGABYTE to GIGABYTE (1024*1024*1024). Is that because of optimizer removes the variable 'byte[] bytes' as it is not used in the rest of the code?Protuberate
"There are a number of scenarios where you may wish to catch an OutOfMemoryError" versus "There is only one good reason to catch an OutOfMemoryError". Make up your mind!!!Redundant
@Chris, Yea Stackoverflow's UI usability isn't exactly top-class.Disproportion
Real-world scenario when you MAY want to catch OutOfMemory error: when it is caused by trying to allocate an array with more than 2G elements. The error name is a bit of a misnomer in this case, but it is still an OOM.Immature
S
31

You can recover from it:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

But does it make sense? What else would you like to do? Why would you initially allocate that much of memory? Is less memory also OK? Why don't you already make use of it anyway? Or if that's not possible, why not just giving the JVM more memory from the beginning on?

Back to your questions:

1: is there any real word scenarios when catching java.lang.OutOfMemoryError may be a good idea?

None comes to mind.

2: if we catching java.lang.OutOfMemoryError how can we sure that catch handler doesn't allocate any memory by itself (any tools or best practicies)?

Depends on what has caused the OOME. If it's declared outside the try block and it happened step-by-step, then your chances are little. You may want to reserve some memory space beforehand:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

and then set it to zero during OOME:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

Of course this makes all with all no sense ;) Just give JVM sufficient memory as your applictation require. Run a profiler if necessary.

Southsoutheast answered 21/4, 2010 at 0:36 Comment(11)
Even reserving space it no guarantee for a working solution though. That space might be taken by some other thread too ;)Acacia
@Wolph: Then give the JVM more memory! O_o All with all it makes indeed no sense ;)Southsoutheast
Who gave the -1 and why? This is a perfectly valid answer and the only one that offers somewhat of a possible solution.Acacia
The first snippet works because the object that triggered the error is a single BIG object (the array). When the catch clause is reached, it has been collected by the memory-hungry JVM. If you were using the same object outside the try block, or in other thread, or in the same catch, JVM doesn't collect it, making it impossible to create a new single object of any kind. the second snippet, for instance, may not work.Repand
why not simply setting it to null ?Disproportion
I am not sure myself, but perhaps when this happens, we want to ask our logger to mail this emergency in the catch block ?Erik
@Wolph, Not me, but of course there are many real world scenarios that we may want to catch OOM. For example, saving all the work before shutting down instead of merely just shutting down.Disproportion
You can't rely on the GC to immediately free that array. Especially in such a fragile state as just after a OOME has been thrown. You couldn't immediately use that memory in the following lines. Could you?Clarita
@Andy I guess, he just gave the GC something to reclaim hoping that the GC gets called when needed and that the GC doesn't just give up. No idea if it works and no idea if calling GC manually would make it better or worse.Columbium
@MisterSmith Your comment doesn't make sense. The large object doesn't exist. It wasn't allocated in the first place: it triggered the OOM, so it certainly doesn't need GC.Chaddy
to answer #1, here's my scenario: User selects a file, and file is stored in byte array to have some operation done on it. If the user selects too large of a file, the catch will notify themLeatri
R
20

In general, it is a bad idea to try to catch and recover from an OOM.

  1. An OOME could also have been thrown on other threads, including threads that your application doesn't even know about. Any such threads will now be dead, and anything that was waiting on a notify could be stuck for ever. In short, your app could be terminally broken.

  2. Even if you do successfully recover, your JVM may still be suffering from heap starvation and your application will perform abysmally as a result.

The best thing to do with an OOME is to let the JVM die.

(This assumes that the JVM does die. For instance OOMs on a Tomcat servlet thread do not kill the JVM. This has been known to cause Tomcat to go into a catatonic state where it won't respond to any requests at all. Not even requests on the management port telling it to restart itself!)

EDIT

I am not saying that it is a bad idea to catch OOM at all. The problems arise when you then attempt to recover from the OOME, either deliberately or by oversight. Whenever you catch an OOM (directly, or as a subtype of Error or Throwable) you should either rethrow it, or arrange that the application / JVM exits.

Aside: This suggests that for maximum robustness in the face of OOMs an application should use Thread.setDefaultUncaughtExceptionHandler() to set a handler that will cause the application to exit in the event of an OOME, no matter what thread the OOME is thrown on. I'd be interested in opinions on this ...

The only other scenario is when you know for sure that the OOM has not resulted in any collateral damage; i.e. you know:

  • what specifically caused the OOME,
  • what the application was doing at the time, and that it is OK to simply discard that computation, and
  • that a (roughly) simultaneous OOME cannot have occurred on another thread.

There are applications where it is possible to know these things, but for most applications you cannot know for sure that continuation after an OOME is safe. Even if it empirically "works" when you try it.

(The problem is that it a formal proof is required to show that the consequences of "anticipated" OOMEs are safe, and that "unanticipated" OOME's cannot occur within the control of a try/catch OOME.)

Redundant answered 20/4, 2010 at 23:30 Comment(10)
Yes, I agree with you. In general, it is bad idea. But why then I have possibility to catch it? :)Manlike
@dotsid - 1) because there are cases where you should catch it, and 2) because making it impossible to catch OOM would have negative impact on the language and/or other parts of the Java runtime.Redundant
You say: "because there are cases where you should catch it". So this was part of my original question. What are the cases when you want to catch OOME?Manlike
@dotsid - see my edited answer. The only case I can think of for catching OOM is when you need to do this to force a multi-threaded application to exit in the event of an OOM. You maybe want to do this for all subtypes of Error.Redundant
This answer doesn't make sense. If all threads caught OOME, none of them would die, and conversely if none of them do, the JVM won't die just because of one OOME.Chaddy
It is not a matter of just catching OOMEs. You also need to recover from them. How does a thread recover if it was supposed to (say) notify another thread ... but it got an OOME? Sure, the JVM won't die. But the application is likely to stop working because of threads stuck waiting for notifications from threads that restarted due to catching an OOME.Redundant
but there it is the real world use case. a catatonic application is a nice example of catch an then system.exit - or not? I am in this state with my tomcat right now. that's why I am here :-)Secessionist
@Secessionist - I don't understand what you are saying. Or asking.Redundant
@StephenC I am using sparkjava (embedded jetty) and a post api. you do not know the size of the payload upfront, yet one payload causes oome which does not kill the vm but put it into a catatonic state. catching the oome and just do System.exit(-1) is kind of a safety belt. Yes, you should never get there in the first place but if so, a catatonic app is worse than a dead one.Secessionist
OK. Good point. (As I said in my Answer: "In general, it is a bad idea to try to catch and recover from an OOM.")Redundant
B
16

Yes, there are real-world scenarios. Here's mine: I need to process data sets of very many items on a cluster with limited memory per node. A given JVM instances goes through many items one after the other, but some of the items are too big to process on the cluster: I can catch the OutOfMemoryError and take note of which items are too big. Later, I can re-run just the large items on a computer with more RAM.

(Because it's a single multi-gigabyte allocation of an array that fails, the JVM is still fine after catching the error and there's enough memory to process the other items.)

Blakley answered 20/1, 2011 at 9:43 Comment(2)
So you have code like byte[] bytes = new byte[length]? Why not just check size at an earlier point?Hobbledehoy
Because the same size will be fine with more memory. I go via the exception because in most cases everything will be fine.Blakley
E
10

There are definitely scenarios where catching an OOME makes sense. IDEA catches them and pops up a dialog to let you change the startup memory settings (and then exits when you are done). An application server might catch and report them. The key to doing this is to do it at a high level on the dispatch so that you have a reasonable chance of having a bunch of resources freed up at the point where you are catching the exception.

Besides the IDEA scenario above, in general the catching should be of Throwable, not just OOM specifically, and should be done in a context where at least the thread will be terminated shortly.

Of course most times memory is starved and the situation is not recoverable, but there are ways that it makes sense.

Ellingson answered 21/4, 2010 at 2:22 Comment(0)
C
8

I came across this question because I was wondering whether it is a good idea to catch OutOfMemoryError in my case. I'm answering here partially to show yet another example when catching this error can make sense to someone (i.e. me) and partially to find out whether it is a good idea in my case indeed (with me being an uber junior developer I can never be too sure about any single line of code I write).

Anyway, I'm working on an Android application which can be run on different devices with different memory sizes. The dangerous part is decoding a bitmap from a file and dislaying it in an ImageView instance. I don't want to restrict the more powerful devices in terms of the size of decoded bitmap, nor can be sure that the app won't be run on some ancient device I've never come across with very low memory. Hence I do this:

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

This way I manage to provide for more and less powerful devices according to their, or rather their users' needs and expectations.

Cornejo answered 28/2, 2012 at 11:50 Comment(2)
Another option would be to calculate how big bitmaps you can handle instead of trying and failing. "Exceptions should be used for exceptional cases" - I think someone said. But I'll say, your solution seems like the easiest way out, perhaps not the best, but probably the easiest.Asiaasian
It depends on the codec. Imagine a bmp of 10MB probably results in only slightly more than 10MB heap, while a JPEG of 10MB will "explode". Same in my case where I want to parse an XML which can vary massively depending on the complexity of the contentEarthshaking
L
6

I have an application that needs to recover from OutOfMemoryError failures, and in single-threaded programs it always works, but sometimes doesn't in multi-threaded programs. The application is an automated Java testing tool that executes generated test sequences to the maximum possible depth on test classes. Now, the UI must be stable, but the test engine can run out of memory while growing the tree of test cases. I handle this by the following kind of code idiom in the test engine:

boolean isOutOfMemory = false;  // flag used for reporting
try {
   SomeType largeVar;
   // Main loop that allocates more and more to largeVar
   // may terminate OK, or raise OutOfMemoryError
}
catch (OutOfMemoryError ex) {
   // largeVar is now out of scope, so is garbage
   System.gc();                // clean up largeVar data
   isOutOfMemory = true;       // flag available for use
}
// program tests flag to report recovery

This works every time in single-threaded applications. But I recently put my test engine into a separate worker-thread from the UI. Now, the out of memory may occur arbitrarily in either thread, and it is not clear to me how to catch it.

For example, I had the OOME occur while the frames of an animated GIF in my UI were being cycled by a proprietary thread that is created behind-the-scenes by a Swing class that is out of my control. I had thought that I had allocated all the resources needed in advance, but clearly the animator is allocating memory every time it fetches the next image. If anyone has an idea about how to handle OOMEs raised in any thread, I would love to hear.

Lanitalank answered 31/5, 2011 at 9:35 Comment(1)
In a single threaded app, if you are no longer using some of the problematic new objects whose creation throwed the error, these may be collected in the catch clause. However, if the JVM detects that the object may be used later, it can't be collected and the app blows up. See my answer in this thread.Repand
L
5

Yes, the real question is "what are you going to do in the exception handler?" For almost anything useful, you'll allocate more memory. If you'd like to do some diagnostic work when an OutOfMemoryError occurs, you can use the -XX:OnOutOfMemoryError=<cmd> hook supplied by the HotSpot VM. It will execute your command(s) when an OutOfMemoryError occurs, and you can do something useful outside of Java's heap. You really want to keep the application from running out of memory in the first place, so figuring out why it happens is the first step. Then you can increase the heap size of the MaxPermSize as appropriate. Here are some other useful HotSpot hooks:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

See the full list here

Ladd answered 21/4, 2010 at 0:41 Comment(1)
It's even worse than your think. Because an OutOfMemeoryError can be thrown at any point in your program (not only from a new statement) your program will be in an undefined state when you catch the exction.Hobbledehoy
R
4

An OOME can be caught, but it is going to be generally useless, depending on if the JVM is able to garbage-collect some objects when reaching the catch block, and how much heap memory is left by that time.

Example: in my JVM, this program runs to completion:

import java.util.LinkedList;
import java.util.List;
                
public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();
            
        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

However, just adding a single line in the catch block will show you what I'm talking about:

import java.util.LinkedList;
import java.util.List;
                
public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();
            
        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error caught!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

The first program runs fine because when reaching the catch block, the JVM detects that the list isn't going to be used anymore (this detection can be also an optimization made at compile time). So when we reach the print statement, the heap memory has been freed almost entirely, so we now have a wide margin of maneuver to continue. This is the best case.

However, if the code is arranged such as the list ll is used after the OOME has been caught, the JVM is unable to collect it. This happens in the second snippet. The OOME, triggered by a new Long creation, is caught, but soon we're creating a new Object (a String in the System.out.println line), and the heap is almost full, so a new OOME is thrown. This is the worst case scenario: we tried to create a new object, we failed, we caught the OOME, yes, but now the first instruction requiring new heap memory (e.g: creating a new object) will throw a new OOME. Think about it, what else can we do at this point with so little memory left? Probably just exiting, hence I said it's useless.

Among the reasons the JVM isn't garbage-collecting resources, one is really scary: a shared resource with other threads also making use of it. Anyonecan see how dangerous catching OOME can be if added to some non-experimental app of any kind.

I'm using a Windows x86 32bits JVM (JRE6). Default memory for each Java app is 64MB.

Repand answered 9/9, 2011 at 7:29 Comment(1)
What if I do ll=null inside the catch block?Stuff
K
3

The only reason i can think of why catching OOM errors could be that you have some massive data structures you're not using anymore, and can set to null and free up some memory. But (1) that means you're wasting memory, and you should fix your code rather than just limping along after an OOME, and (2) even if you caught it, what would you do? OOM can happen at any time, potentially leaving everything half done.

Keats answered 21/4, 2010 at 0:10 Comment(0)
P
3

For the question 2 I already see the solution I would suggest, by BalusC.

  1. Is there any real word scenarios when catching java.lang.OutOfMemoryError may be a good idea?

I think I just came across a good example. When awt application is dispatching messages the uncatched OutOfMemoryError is displayed on stderr and the processing of the current message is stopped. But the application keeps running! User may still issue other commands unaware of the serious problems happening behind the scene. Especially when he cannot or does not observe the standard error. So catching oom exception and providing (or at least suggesting) application restart is something desired.

Pecuniary answered 25/7, 2012 at 6:50 Comment(0)
L
3

I just have a scenario where catching an OutOfMemoryError seems to make sense and seems to work.

Scenario: in an Android App, I want to display multiple bitmaps in highest possible resolution, and I want to be able to zoom them fluently.

Because of fluent zooming, I want to have the bitmaps in memory. However, Android has limitations in memory which are device dependent and which are hard to control.

In this situation, there may be OutOfMemoryError while reading the bitmap. Here, it helps if I catch it and then continue with lower resolution.

Lapsus answered 30/3, 2015 at 19:43 Comment(0)
S
1
  1. Depends on how you define "good". We do that in our buggy web application and it does work most of the time (thankfully, now OutOfMemory doesn't happen due to an unrelated fix). However, even if you catch it, it still might have broken some important code: if you have several threads, memory allocation can fail in any of them. So, depending on your application there is still 10--90% chance of it being irreversibly broken.
  2. As far as I understand, heavy stack unwinding on the way will invalidate so many references and thus free so much memory you shouldn't care about that.

EDIT: I suggest you try it out. Say, write a program that recursively calls a function that allocates progressively more memory. Catch OutOfMemoryError and see if you can meaningfully continue from that point. According to my experience, you will be able to, though in my case it happened under WebLogic server, so there might have been some black magic involved.

Sardonyx answered 20/4, 2010 at 23:24 Comment(0)
L
-1

You can catch anything under Throwable, generally speaking you should only catch subclasses of Exception excluding RuntimeException (though a large portion of developers also catch RuntimeException... but that was never the intent of the language designers).

If you were to catch OutOfMemoryError what on earth would you do? The VM is out of memory, basically all you can do is exit. You probably cannot even open a dialog box to tell them you are out of memory since that would take memory :-)

The VM throws an OutOfMemoryError when it is truly out of memory (indeed all Errors should indicate unrecoverable situations) and there should really be nothing you can do to deal with it.

The things to do are find out why you are running out of memory (use a profiler, like the one in NetBeans) and make sure you don't have memory leaks. If you don't have memory leaks then increase the memory that you allocate to the VM.

Lactate answered 20/4, 2010 at 23:24 Comment(3)
A misconception your post perpetuates is that OOM indicates the JVM is out of memory. Instead, it actually indicates that the JVM could not allocate all the memory it was instructed to. That is, if the JVM has 10B of space and you 'new up' a 100B object, it will fail, but you could turn around and 'new up' a 5B object and be fine.Enrollment
And if I only needed 5B they why am I asking for 10B? If you are doing allocation based on trial and error you are doing it wrong.Lactate
I guess Tim meant that you could still perform some work even with an OutOfMemory situation. There could be enough memory left to open a dialog, for instance.Technicality

© 2022 - 2024 — McMap. All rights reserved.