Friday, April 12, 2013

StackOverflowError without recursion

    I believe that the great majority of programmers has already seen StackOverflowError with beautiful stacktrace. The flow is usually stuck deep inside the invocation of the same, recursive method. However, such an error can be produced quite easy without recursion.
Stack memory is like any other memory - it can be saturated with data. When the data is too big to fit into memory - an error is thrown. The size of the stack memory is controlled by means of:
-Xss
parameter that can be passed to JVM. Let's execute (on JDK7u10-64bit):
java -Xss=128k StackOverflowErrorExample
with StackOverflowErrorExample class:
package test;

public class StackOverflowErrorExample {
    public static void main(String[] args) {        
        Object o1=null,o2=null,...,o12999=null;
    }
}
The execution resulted in:
Exception in thread "main" java.lang.StackOverflowError
    at test.StackOverflowErrorExample.main(StackOverflowErrorExample.java:5)
How could that happen? We did not allocate any object and there is only one method invocation. But wait... We have already allocated a lot of references! They reside in the stack memory. Object reference in 32-bit JVM occupies 4 bytes of memory. The same reference in 64-bit JVM needs 8 bytes of memory - however it can be limited to 4 bytes with
-XX:UseCompressedOops
flag but the heap can be as big as 32GB then.
We allocated a lot of references plus the invocation of main method - it consumed all of the stack memory. It is crucial to remember that stack memory contains:
  • method local references and method local primitives,
  • references of method parameters,
  • method invocations.
You may ask if '=null' part is necessary. Well, it is necessary to fool java compiler. JVM code is going to tell us the truth. Without RHS (right hand side) of the equation the JVM code generated by javac looks like:
javap -c StackOverflowErrorExample
Compiled from "StackOverflowErrorExample.java"
public class test.StackOverflowErrorExample {
  public test.StackOverflowErrorExample();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":
()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: return
}
Java compiler did a good job - it got rid of the references that are not initialized and never used. With RHS part it looks better, at least for our example:
javap -c StackOverflowErrorExample
...
    48748: astore_w      #9853              // Utf8 a9834

    48752: aconst_null

    48753: astore_w      #9854              // Utf8 a9835

    48757: aconst_null

    48758: astore_w      #9855              // Utf8 a9836 
...
You may also ask if:
Object[] o = new Object[13000];
will give us the same effect of StackOverflowException. In the last example we have only one reference allocated and it goes on the stack. new Object[13000] goes on the heap because arrays are objects and the heap is their natural environment for living. Hence, we can eventually end up with java.lang.OutOfMemoryError: Java heap space.

1 comment :