Wednesday, May 30, 2012

What happens when there"s insufficient memory to throw an OutOfMemoryError?


I am aware that every object requires heap memory and every primitive/reference on the stack requires stack memory.



When I attempt to create an object on the heap and there's insufficient memory to do so, the JVM creates an java.lang.OutOfMemoryError on the heap and throws it to me.



So implicitly, this means that there is some memory reserved by the JVM on startup.



What happens when this reserved memory is used up (it would definitely be used up, read discussion below) and the JVM does not have enough memory on the heap to create an instance of java.lang.OutOfMemoryError ?



Does it just hang? Or would he throw me a null since there's no memory to new an instance of OOM ?




try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}



==



Why couldn't the JVM reserve sufficient memory?



No matter how much memory is reserved, it is still possible for that memory to be used up if the JVM does not have a way to "reclaim" that memory:




try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}



Or more concisely:




private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}


Source: Tips4all

10 comments:

  1. The JVM never really runs out of memory. It does memory computation of the heap stack in advance.

    The Structure of the JVM, Chapter 3, section 3.5.2 states:



    If Java virtual machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available
    to effect the expansion, or if insufficient memory can be made
    available to create the initial Java virtual machine stack for a new
    thread, the Java virtual machine throws an OutOfMemoryError.



    For Heap, Section 3.5.3.



    If a computation requires more heap than can be made available by the automatic storage management system, the Java virtual machine
    throws an OutOfMemoryError.



    So, it does a computation in advance before doing allocation of the object.



    What happens is that the JVM tries to allocate memory for an object in the memory called Permanent Generation region (or PermSpace). If allocation fails (even after the JVM invokes the Garbage Collector to try & allocate free space), it throws an OutOfMemoryError. Even exceptions requires a memory space so the error will be thrown indefinitely.

    Further reading.? Furthermore, OutOfMemoryError can occur in different JVM structure.

    ReplyDelete
  2. Graham Borland seems to be right: at least my JVM apparently re-uses OutOfMemoryErrors. To test this, I wrote a simple test program:

    class OOMTest {
    private static void test (OutOfMemoryError o) {
    try {
    for (int n = 1; true; n += n) {
    int[] foo = new int[n];
    }
    } catch (OutOfMemoryError e) {
    if (e == o)
    System.out.println("Got the same OutOfMemoryError twice: " + e);
    else test(e);
    }
    }
    public static void main (String[] args) {
    test(null);
    }
    }


    Running it produces this output:

    $ javac OOMTest.java && java -Xmx10m OOMTest
    Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space


    BTW, the JVM I'm running (on Ubuntu 10.04) is this:

    $ java -version
    java version "1.6.0_26"
    Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
    Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)




    Edit: I tried to see what would happen if I forced the JVM to run completely out of memory using the following program:

    class OOMTest2 {
    private static void test (int n) {
    int[] foo;
    try {
    foo = new int[n];
    test(n * 2);
    }
    catch (OutOfMemoryError e) {
    test((n+1) / 2);
    }
    }
    public static void main (String[] args) {
    test(1);
    }
    }


    As it turns out, it seems to loop forever. However, curiously, trying to terminate the program with Ctrl+C doesn't work, but only gives the following message:

    Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

    ReplyDelete
  3. Most runtime environments will pre-allocate on startup, or otherwise reserve, enough memory to deal with memory starvation situations. I imagine most sane JVM implementations would do this.

    ReplyDelete
  4. Last time I was working in Java and using a debugger, the heap inspector showed that the JVM allocated an instance of OutOfMemoryError on startup. In other words, it allocates the object before your program has a chance to start consuming, let alone run out of, memory.

    ReplyDelete
  5. From the JVM Spec, Chapter 3.5.2:


    If Java virtual machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java virtual machine stack for a new thread, the Java virtual machine throws an OutOfMemoryError.


    Every Java Virtual Machine has to guarantee that it will throw an OutOfMemoryError. That implies, that it has to be capable of creating an instance of OutOfMemoryError (or haveing on created in advance) even if there's no heap space left.

    Although it does not have to guarantee, that there's enough memory left to catch it and print a nice stacktrace...

    Addition

    You added some code to show, that the JVM might run out of heap space if it had to throw more than one OutOfMemoryError. But such an implementation would violate the requirement from above.

    There is no requirement that the thrown instances of OutOfMemoryError are unique or created on demand. A JVM could prepare exactly one instance of OutOfMemoryError during startup and throw this whenever it runs out of heap space - which is once, in normal environment. In other words: the instance of OutOfMemoryError that we see could be a singleton.

    ReplyDelete
  6. Interesting question :-). While the others have given good explanations of the theoretical aspects, I decided to try it out. This is on Oracle JDK 1.6.0_26, Windows 7 64 bit.

    Test setup

    I wrote a simple program to exhaust memory (see below).

    The program just creates a static java.util.List, and keeps stuffing fresh strings into it, until OOM is thrown. It then catches it and continues stuffing in an endless loop (poor JVM...).

    Test result

    As one can see from the output, the first four times OOME is thrown, it comes with a stack trace. After that, subsequent OOMEs only print java.lang.OutOfMemoryError: Java heap space if printStackTrace() is invoked.

    So apparently the JVM makes an effort to print a stack trace if it can, but if memory is really tight, it just omits the trace, just like the other answers suggest.

    Also interesting is the hash code of the OOME. Note that the first few OOME all have different hashes. Once the JVM starts omitting stack traces, the hash is always the same. This suggests that the JVM will use fresh (preallocated?) OOME instances as long as possible, but if push comes to shove, it will just reuse the same instance rather than having nothing to throw.

    Output

    Note: I truncated some stack traces to make the output easier to read ("[...]").

    iteration 0
    iteration 100000
    iteration 200000
    iteration 300000
    iteration 400000
    iteration 500000
    iteration 600000
    iteration 700000
    iteration 800000
    iteration 900000
    iteration 1000000
    iteration 1100000
    iteration 1200000
    iteration 1300000
    iteration 1400000
    iteration 1500000
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
    Keep on trying...
    java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
    java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    [...]
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
    Keep on trying...
    java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    [...]
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
    Keep on trying...
    java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    [...]
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
    Keep on trying...
    java.lang.OutOfMemoryError: Java heap space
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
    Keep on trying...
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
    Keep on trying...
    java.lang.OutOfMemoryError: Java heap space
    Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
    Keep on trying...
    [...]


    The program

    public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
    exhaustMemory();
    }

    private static void exhaustMemory() {
    try {
    gobbleUpMemory();
    } catch (OutOfMemoryError e) {
    System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
    e.printStackTrace();
    System.out.println("Keep on trying...");
    exhaustMemory();
    }
    }

    private static void gobbleUpMemory() {
    for (int i = 0; i < 10000000; i++) {
    list.add(new String("some random long string; use constructor to force new instance"));
    if (i % 10000000== 0) {
    System.out.println("iteration "+i);
    }
    }

    }
    }

    ReplyDelete
  7. I am pretty sure, the JVM will make absolutely sure that it has at least enough memory to throw an exception before it runs out of memory.

    ReplyDelete
  8. Exceptions indicating an attempt to violate the boundaries of a managed-memory environment are handled by the runtime of said environment, in this case the JVM. The JVM is its own process, which is running your application's IL. Should a program attempt to make a call that extends the call stack beyond the limits, or allocate more memory than the JVM can reserve, the runtime itself will inject an exception, which will cause the call stack to be unwound. Regardless of the amount of memory your program currently needs, or how deep its call stack, the JVM will have allocated enough memory within its own process bounds to create said exception and inject it into your code.

    ReplyDelete
  9. You seem to be confusing the virtual memory reserved by the JVM in which the JVM runs Java programs with the host OS's native memory in which the JVM is run as a native process. The JVM on your machine is running in the memory managed by the OS, not in the memory the JVM has reserved to run Java programs.

    Further reading:


    http://java.sys-con.com/node/1229281
    http://publib.boulder.ibm.com/infocenter/javasdk/tools/index.jsp?topic=%2Fcom.ibm.java.doc.igaa%2F_1vg000121410cbe-1195c23a635-7ffa_1001.html


    And as a final note, trying to catch a java.lang.Error (and its descendant classes) in order to print a stacktrace may not give you any useful information. You want a heap dump instead.

    ReplyDelete
  10. To further clarify @Graham Borland's answer, functionally, the JVM does this at startup:

    private static final OutOfMemoryError OOME = new OutOfMemoryError();


    Later, the JVM executes one of the following Java bytecodes: 'new', 'anewarray', or 'multianewarray'. This instruction causes the JVM to perform a number of steps in an out of memory condition:


    Invoke a native function, say allocate(). allocate() attempts to allocate memory for some a new instance of a particular class or array.
    That allocation request fails, so the JVM invokes another native function, say doGC(), which attempts to do garbage collection.
    When that function returns, allocate() tries to allocate memory for the instance once again.
    If that fails(*), then the JVM, within allocate(), simply does a throw OOME;, referring to the OOME that it instantiated at startup. Note that it did not have to allocate that OOME, it just refers to it.


    Obviously, these are not literal steps; they'll vary from JVM to JVM in implementation, but this is the high-level idea.

    (*) A significant amount of work happens here before failing. The JVM will attempt to clear SoftReference objects, attempt allocation directly into the tenured generation when using a generational collector, and possibly other things, like finalization.

    ReplyDelete