Catching java.lang.OutOfMemoryError?

前端 未结 14 1450
Happy的楠姐
Happy的楠姐 2020-11-22 06:01

Documentation for java.lang.Error says:

An Error is a subclass of Throwable that indicates serious problems that a reasonable application

相关标签:
14条回答
  • 2020-11-22 06:41
    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.

    0 讨论(0)
  • 2020-11-22 06:42

    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.

    0 讨论(0)
  • 2020-11-22 06:42

    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.)

    0 讨论(0)
  • 2020-11-22 06:43

    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.

    0 讨论(0)
  • 2020-11-22 06:44

    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.

    0 讨论(0)
  • 2020-11-22 06:45

    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, and this leads to the Tomcat going into a catatonic state where it won't respond to any requests ... not even requests to restart.)

    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.)

    0 讨论(0)
提交回复
热议问题