Stack overflow error handling in finally block

I have a program in java, which runs infinite times.

Program code:

void asd()
{
    try
    {
        //inside try block
        System.out.println("Inside try !!!");
        asd();
    }
    finally
    {
        //inside finally
        System.out.println("Inside finally !!!");
        asd();
    }
}

OUTPUT : this program runs infinitely, by constantly printing both the sysouts.

My question : At some point, it starts throwing StackOverflowErrors from the try block and so it reaches the finally block, where we are again calling this function recursively. But how is the recursive function in the finally block being executed as we already facing a StackOverflowError?

How does the JVM handle this situation? Will the same behavior occur if we get OutOfMemoryErrors too?


The problem is that your example program is pathological. It won't work, and it can't work.

But how the recursive function in the finally block gets executed as we already facing StackOverflowError .?

There is a rather complicated sequence of calls going on. Lets assume that the stack can hold 3 frames. A "hand execution" gives us a sequence of calls / call-stack snapshots as follows:

asd()
asd() > try
asd() > try > asd() 
asd() > try > asd() > try 
asd() > try > asd() > try > asd() // Stack Overflow!
asd() > try > asd() > finally
asd() > try > asd() > finally > asd() // Stack Overflow!
asd() > finally
asd() > finally > asd()
asd() > finally > asd() > try
asd() > finally > asd() > try > asd() // Stack Overflow!
asd() > finally > asd() > finally
asd() > finally > asd() > finally > asd() // Stack Overflow!

END

As you can see with a stack of depth 3, we made 7 calls, 4 of which failed with a stack overflow. If you do the hand execution for a stack of depth 4, you will get 15 calls, 5 => 31. The pattern is N => 2**N - 1 calls .

In your case, the default stack is going to be able to accommodate hundreds, or even thousands of recursive calls.

Say N = 100. 2**100 is a very large number of calls. It is not infinite, but you will probably be dead before the program terminates.

How does the JVM handle this situation?

As above. The JVM is not doing anything special. The "effectively infinite loop" behaviour is entirely down to the way that your program is written.

Will the same behaviour occur if we get OutOfMemoryError s too?

Erm ... it depends on your program. But I'm sure you could concoct an example program that exhibited a similar pattern of behaviour.


Suppose the program is executing the asd() method and the stack space is just about to end. Also suppose that instead of "inside try" and "inside finally" the method prints a counter that tells how far the stack you are:

void asd(int i){
    try{
        //inside try block
        System.out.print(i);
        System.out.println("t");
        asd(i+1);
    }
    finally{
        //inside finally
        System.out.print(i);
        System.out.println("f");
        asd(i+1);
    }
}

}

Now this is what the program does when it's just about to run out of stack space, when i is 9154.

The call to println("t") outputs the characters, and then goes on to call the println() method. This makes the program run out of stack space, so the execution is moved to the finally block. The call to println there again runs out of stack space when printing the new line. The error is thrown again, and the execution moves on to the finally in the activation frame above the current method call. This makes the program print f for the second time, and since we've popped an activation frame from the stack this call now completes normally, and prints out a new line. The program has so far given this output:

...
9152t
9153t
9154t9154f9153f              // notice we jumped to i=1953

Now, the method calls itself again, now from the finally block. The situation with the stack space is just like before, so the program executes like above, except that, since we were in a finally block for the method call of i=1953, the program execution ends up in the finally block of the method call of i=1952:

9154t9154f9152f

The finally block of i=9152 calls asd again, passing i=9153, and since now there's enough stack space to print a complete line the method outputs from the try-block:

9153t

and then goes on to call itself, and in this call will end up running out of stack space again, giving the output:

9154t9154f9153f

... and the rest of the output can be explained in a similar manner:

9154t9154f9151f
9152t
9153t
9154t9154f9153f
9154t9154f9152f
9153t
9154t9154f9153f
9154t9154f9150f
9151t
9152t
9153t
...

What's important to notice is:

  • The finally block is executed even in the case of a StackOverflowError .
  • A program that has run into a StackOverflowError can be in an unpredictable state. Here the only observable effect is the fact that println doesn't output a line break. In a more complex program this could mean that you can't trust the state of anything that the program had been working on, and that the safest thing to do is to bail out completely.

  • Errors, ie OutOfMemoryError, StackOverflowError, etc. are not intended to be handled. They leave JVM in undefined state, nothing is guaranteed. At this point your application must simply terminate and you must fix the problem which lead to this.

    Your application should not try to handle errors OR run after one happened. If you're calling the recursive function at this point then you are the one to blame. The result is not predictable.

    See "11.1.1. The Kinds of Exceptions": http://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html

    链接地址: http://www.djcxy.com/p/80458.html

    上一篇: 什么时候发生StackOverflowError?

    下一篇: 在finally块中堆栈溢出错误处理