102

I'm surprised at how it is possible to continue execution even after a StackOverflowError has occurred in Java.

I know that StackOverflowError is a sublass of the class Error. The class Error is decumented as "a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch."

This sounds more like a recommendation than a rule, subtending that catching a Error like a StackOverflowError is in fact permitted and it's up to the programmer's reasonability not to do so. And see, I tested this code and it terminates normally.

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

How can this be? I think by the time the StackOverflowError is thrown, the stack should be so full that there is no room for calling another function. Is the error handling block running in a different stack, or what is going on here?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
user3370796
  • 1,060
  • 1
  • 7
  • 7
  • 4
    Related: http://codegolf.stackexchange.com/questions/21114/weirdest-way-to-produce-a-stack-overflow – ntoskrnl Mar 02 '14 at 15:14
  • 57
    I make errors on StackOverflow all the time. Doesn't stop me from coming back though. –  Mar 03 '14 at 02:45
  • 10
    Yo dawg... I heard you liked stack overflows so we put a stack overflow in your stackoverflow.com ! – Pierre Henry Mar 03 '14 at 09:17
  • Because modern architectures use Frame Pointers to facilitate unwinding stacks, even partial ones. As long as the code+context to do that don't have to be allocated dynamically off the stack, there shouldn't be a problem. – RBarryYoung Mar 03 '14 at 16:24

5 Answers5

121

When the stack overflows and StackOverflowError is thrown, the usual exception handling unwinds the stack. Unwinding the stack means:

  • abort the execution of the currently active function
  • delete its stack frame, proceed with the calling function
  • abort the execution of the caller
  • delete its stack frame, proceed with the calling function
  • and so on...

... until the exception is caught. This is normal (in fact, necessary) and independent of which exception is thrown and why. Since you catch the exception outside of the first call to foo(), the thousands of foo stack frames that filled the stack have all been unwound and most of the stack is free to be used again.

  • Nice explanation, but could do with a little "airing" of the text :) – fge Mar 02 '14 at 14:09
  • 1
    @fge Feel free to edit, I considered a paragraph break but couldn't find a place where it looked good. –  Mar 02 '14 at 14:21
  • 1
    You could use bullet points... I am reluctant to editing post of other people ;) – fge Mar 02 '14 at 14:22
  • 2
    The point is that the innermost `foo` terminated with undefined state, so any object it may have touched must be assumed to be broken. As you do not know which function the stack overflow occured in, only that it must be a descendant of the `try` block that caught it, any object that may be modified by any method reachable from there is now suspect. Usually it is not worthwhile to find out what happened and try to fix it. – Simon Richter Mar 02 '14 at 18:42
  • @SimonRichter That's true, though it's a more general exception safety problem, any exception can cause such problems unless the code involved carefully avoids it. But why are you attaching that to this answer? –  Mar 02 '14 at 18:46
  • 3
    @delnan, I think the answer is incomplete without also going into detail why this is a bad idea. The difference to an explicitly thrown exception is that `Error`s cannot be anticipated even when writing exception safe code. – Simon Richter Mar 02 '14 at 18:53
  • 1
    @SimonRichter No, the question is pretty specific. It's *not* about handling `Error`s. The OP is asking *only* about `StackOverflowError`, and it's asking a specific thing of the handling of *this* error: how can a method call *not* fail when this error is caught. – Bakuriu Mar 02 '14 at 19:33
  • 1
    @Bakuriu the point is that just because you have ended up in a `catch` block, even if execution continues without a crash, does not mean that the error has been "handled", or that you sanely *can*. Exception safety is not trivial to design. – Karl Knechtel Mar 02 '14 at 23:23
  • 1
    @SimonRichter: Even if one presumes that any object which was altered by code which threw an unexpected exception is likely corrupt, there are many methods which won't touch anything except a new object under construction. Consequently, if code can deal with the inability to construct the new object, there won't be any other problem. That having been said, I wish there was a nice way via which methods could ask the system to throw a `LowstackException` before entering a `try`/`finally` block whose `finally` block might not complete. – supercat Mar 03 '14 at 02:31
  • In a traditional environment, a stack overflow error will happen when a stack write is out of bounds. This would be a non-recoverable error because the last stack write may have been written to (for example) read-only memory. ava on the other hand might implement stack overflow detection by looking at the stack pointer, such that the error is triggered before illegal memory is accessed, and recover safely. This is an aspect of the issue that this answer doesn't address. – nitro2k01 Mar 03 '14 at 14:51
  • 1
    @nitro2k01 I think that for the purpose of this question, this issue does not arise. The JVM is responsible for making sure it keeps working even after a stack overflow; *how* this is achieved internally is interesting in its own right but not relevant for this question. –  Mar 03 '14 at 16:16
  • @delnan: A problem with stack overflow and out-of-memory conditions is that they can occur *while processing other exceptions*, and thus when such an exception occurs one can't really be certain about what else may have happened. If there were a per-thread flag that indicated whether constructors should fail if memory falls belows a certain amount and expect code to deal with it, or whether constructors which must succeed or crash the application, and there were a construct to run a block of code in either mode (and then return to the previous setting), then... – supercat Mar 10 '14 at 17:25
  • ...recovery from low-memory conditions could be much more manageable. Actually, it might be helpful to have multiple thresholds available, so that allocations associated with documents that might be too big for RAM would have the lowest amount of RAM available (say 90%), those associated with the UI would have a medium threshold (say 95%), and those associated with exception recovery would have all RAM available. – supercat Mar 10 '14 at 17:27
23

When the StackOverflowError is thrown, the stack is full. However, when it's caught, all those foo calls have been popped from the stack. bar can run normally because the stack is no longer overflowing with foos. (Note that I don't think the JLS guarantees you can recover from a stack overflow like this.)

user2357112
  • 260,549
  • 28
  • 431
  • 505
12

When the StackOverFlow occurs, the JVM will pop down to the catch, freeing the stack.

In you example, it get rids of all the stacked foo.

Nicolas Defranoux
  • 2,646
  • 1
  • 10
  • 13
8

Because the stack doesn't actually overflow. A better name might be AttemptToOverflowStack. Basically what it means is that the last attempt to adjust the stack frame errs because there isn't enough free space left on the stack. The stack could actually have lots of space left, just not enough space. So, whatever operation would have depended upon the call succeeding (typically a method invocation), never gets exectued and all that is left is for the program to deal with that fact. Which means that it is really no different from any other exception. In fact, you could catch the exception in the function that is making the call.

jmoreno
  • 12,752
  • 4
  • 60
  • 91
  • 1
    Just be careful if you do this that your exception handler doesn't require more stack space than is available! – Vince Mar 03 '14 at 14:16
7

As has already been answered, it is possible to execute code, and in particular to call functions, after catching a StackOverflowError because the normal exception handling procedure of the JVM unwinds the stack between the throw and the catch points, freeing stack-space for you to use. And your experiment confirms that is the case.

However, that is not quite the same as saying that it is, in general, possible to recover from a StackOverflowError.

A StackOverflowError IS-A VirtualMachineError, which IS-AN Error. As you point out, Java provides some vague advice for an Error:

indicates serious problems that a reasonable application should not try to catch

and you, reasonably, conclude that should sounds like catching an Error might be OK in some circumstances. Note that performing one experiment does not demonstrate that something is, in general, safe to do. Only the rules of the Java language and the specifications of the classes you use can do that. A VirtualMachineError is a special class of exception, because the Java Language Specification and the Java Virtual Machine Specification provide information about the semantics of this exception. In particular, the latter says:

A Java Virtual Machine implementation throws an object that is an instance of a subclass of the class VirtualMethodError when an internal error or resource limitation prevents it from implementing the semantics described in this chapter. This specification cannot predict where internal errors or resource limitations may be encountered and does not mandate precisely when they can be reported. Thus, any of the VirtualMethodError subclasses defined below may be thrown at any time during the operation of the Java Virtual Machine:

...

  • StackOverflowError: The Java Virtual Machine implementation has run out of stack space for a thread, typically because the thread is doing an unbounded number of recursive invocations as a result of a fault in the executing program.

The crucial problem is that you "cannot predict" where or when a StackOverflowError will be thrown. There are no guarantees about where it will not be thrown. You can not rely on it being thrown on entry to a method, for example. It could be thrown at a point within a method.

This unpredictability is potentially disastrous. As it can be thrown within a method, it could be thrown part way through a sequence of operations that the class considers to be one "atomic" operation, leaving the object in a partially modified, inconsistent, state. With the object in an inconsistent state, any attempt to use that object could result in erroneous behaviour. In all practical cases you can not know which object is in an inconsistent state, so you have to assume that no objects are trustworthy. Any recovery operation or attempt to continue after the exception is caught could therefore have erroneous behaviour. The only safe thing to do is therefore to not catch a StackOverflowError, but rather to allow the program to terminate. (In practice you might attempt to do some error logging to assist troubleshooting, but you can not rely on that logging operating correctly). That is, you can not reliably recover from a StackOverflowError.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
  • This is a very good answer. IMHO, you shouldn't catch uncecked exceptions anyway, because of the problem you describe: you cannot be sure if everything in the stack trace that was unwinded is still stable. Especially widely used, generic Exceptions like IllegalStateException or NullReferenceException. Problably errors are even worse, because framework/library authors probably don't expect that an application would try to recover from them. – Stefan Steinegger Aug 25 '23 at 09:40