12

As far as I know this code should throw StackOverflowError, but it isn't. What could be the reason?

public class SimpleFile {

    public static void main(String[] args) {
        System.out.println("main");

        try{
        SimpleFile.main(args);
            }

        catch(Exception e){
            System.out.println("Catch");
        }

        finally{
            SimpleFile.main(args);
        }
    }

}
VedantK
  • 9,728
  • 7
  • 66
  • 71
FIRST LAST
  • 121
  • 3
  • 6
    I guess its because main being a static method is not created on stack instead its created on PermGen section of heap so everytime we call it previous instance gets lost, if u put this code `new SimpleFile().abc()` immediately after ur main method and create method `void abc() {abc(); }` then definetly its throwing stackoverflow exception because non-static methods are created on stack. – Aamir Dec 03 '15 at 05:49
  • @3kings that makes no sense. are you saying simple methods like `void foo() {System.out.println("foo"); foo(); }` wouldn't throw stack overflow? – eis Dec 03 '15 at 06:05
  • @Aamir wouldn't that be an answer? – eis Dec 03 '15 at 06:06
  • @eis I was not sure if this was the reason – Aamir Dec 03 '15 at 06:08
  • @Aamir I guess not `public class A { public static void main(String[] args) { A.test(); } public static void test() { A.main(null); } }` – SpringLearner Dec 03 '15 at 06:08

5 Answers5

5

An Error is not an Exception. So catching any exception won't catch the StackOverflowError.

So lets start by fixing the "obvious mistake" - (this code is inadvisable as stated later in this answer):

    catch(Throwable e){
        System.out.println("Catch");
    }

If you make this change you will find the code still does not print. But it doesn't print for a very different reason...

Catching any ERROR (including a StackOverflowError) is highly discouraged. But here you are not only catching one, you're catching one as it happens at the top of the stack. Even with your code (without the above change) the error is effectively caught by the finally block.

A StackOverflowError occurs when the stack is full and you try to add more to it. So when you catch the error the stack is still full. You can not call any method (even to print to the console) because the stack is full. So a second StackOverflowError is thrown in the catch before it has successfully printed.

The result of this is that it:

  1. Catches the error
  2. trys to print the error
  3. causes another error because it can't print
  4. calls finally, because finally is always called.
  5. causes another error because it cant call main
  6. cascades the error back to the previous call which runs into the same error.

The key here is that eventually it will start printing something. But the call to print uses a lot of stack space and your code will have to recurs and error through the above points for a very long time before it ever frees up enough stack space to print. According to Holger's comment With Oracle’s Java 8 to put the number of main stack frames needed for a println stack frame close to 50.

250 = 1,125,899,906,842,624

This is why YOU SHOULD NEVER CATCH ERRORS.

There are only a handful of excuses that allow you to break this rule, and you've discovered first hand what can go wrong if you do break it.

Philip Couling
  • 13,581
  • 5
  • 53
  • 85
  • That's right - but shouldn't the code then go into the finally block and then throw another StackOverflowError when it attempts to call SimpleFile.main(args); again? – matt freake Dec 03 '15 at 09:53
  • Yes, that's what I'm investigating now. It looks like a bit of a bug. Maybe with the optimiser. – Philip Couling Dec 03 '15 at 10:01
  • I've got it! Catching throwable is BAD. Catching a stack overflow exception as it happens is VERY BAD. I've updated my answer. – Philip Couling Dec 03 '15 at 10:26
  • @Holger As many discovered, simply changing from `catch (Exception e)` to `catch (Throwable e)` was not sufficient to fix the problem. It is on first glance the problem with what the OP's code but there is more at play than just that. The code change got lost my edits. I can put it back in to make the answer more clear. – Philip Couling Dec 03 '15 at 11:49
  • 1
    Now it’s understandable. You may add that the `println` requires much more stack size than the `main` method, so between the first occurrence of a `StackOverflowError` and the first printing of a `Catched` it will perform `2ⁿ` more invocations of `main` where `n` is determined by the difference of the required stack size of flat `main` and a complete `print` operations. With Oracle’s Java 8, `n` seems to be close to `50` here… – Holger Dec 03 '15 at 12:16
3

Actually you got java.lang.Stackoverflow

You can run this sample code:

public class SimpleFile {
    public static void main(String[] args) {
       System.out.println("main ");
       try{
          SimpleFile.main(args);
       }finally{
          try{
             SimpleFile.main(args);
          }catch(Error e2){
             System.out.println("finally");
             throw e2;
          }
       }
   }
}

PS

More details: your program prints a lot of main messages and after this you receives stack overflow error for first time and go to finally block. It means that you decrease stack size and now you can call something. But you call itself in finally block and get stack overflow again. Most surprising for me was unstable output:

 main 
 main main finally
 main 
 main main finallyfinallyfinally
 main 
 main 
sibnick
  • 3,995
  • 20
  • 20
3

First, you have a catch clause which doesn’t catch Errors:

catch(Exception e){
    System.out.println("Catch");
}

Since Errors are not Exceptions, this does not catch StackOverflowErrors and the print statement will not get executed. If an Error is not catched, it’s stack trace will get printed by the default handler of a thread, if it ever reaches that point. But you have another clause:

finally{
    SimpleFile.main(args);
}

The code of the finally clause will always get executed when the try block completes, whether normally or exceptionally. Since your try block contains an infinite recursion, it will never complete normally.

In the exceptional case, i.e. when a StackOverflowError is thrown, the finally action will go into into an infinite recursion again, which may again eventually fail with a StackOverflowError, but since it bears the same finally block, it will also go into the infinite recursion again.

Your program is basically saying “perform an infinite recursion, then another infinite recursion”. Note that you can’t distinguish from the printing of "main" whether the program runs in the primary infinite recursion or one triggered from a finally block (except that line breaks might be missing if a stackoverflow happens right in between the println execution).

So if we assume that a particular JVM has a limit of 1000 nested invocations, your program will perform 2¹⁰⁰⁰ invocations of your main method (quantification). Since your main method actually does nothing, an optimizer could elide even this incredible number of invocations, but this optimization also implies that the required stack size vanishes and thus, an even higher number of recursive invocations becomes possible. Only a JVM implementation enforcing an intentional limit on the supported number of recursive invocations, independent from the actually required stack space, could enforce this program to ever terminate.

But note, that in the case of an infinite recursion, there is no guaranty to get a StackOverflowError at all. Theoretically, a JVM having an infinite stack space would be a valid implementation. This implies that a JVM, practically optimizing recursive code to run without requiring additional stack space, would be valid too.

So for a typical implementation like Oracle’s JVM, it is practically impossible for your program to ever report a StackOverflowError. They happen, but are shadowed by your follow-up recursions in the finally block, thus are never reported.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I like this exploration of the theory. I'm not much of a fan of hypothetical situations based on infinity. They have a tenancy to trick you into thinking something is possible for finite situations which really isn't. A JVM with an unlimited stack would still fail (hopefully reporting a `StackOverflowError`) if only when ran out of RAM or swap space. – Philip Couling Dec 03 '15 at 14:37
  • @couling: the JVM with infinite stack space may also have the required infinite RAM. It doesn’t matter that such a thing doesn’t exist. It’s only a mental model to explain, that it is legal for a JVM to transform this recursion into an infinite loop printing `"main"` forever without ever throwing. And such a transformation *exists*. It’s called “tail call optimization” and while Oracle’s JVM doesn’t have it, others may have. – Holger Dec 03 '15 at 15:01
  • @couling: not exactly. This roulette strategy is guaranteed to win in the case it completes. But it is not guaranteed to ever complete… Regarding stack traces, a JVM could simply count the number of recursions and repeat the relevant entry in the stack as needed. Note that Oracle’s current implementation *has* a limit on the number of stack trace entries, and in some situations, it already doesn’t provide a stack trace at all, even without tail call optimization. – Holger Dec 03 '15 at 16:29
  • @couling: you are changing the definition. The roulette strategy works, if you have infinite money, infinite time and no limit for the bets. Once you confront one of these constraints with reality, this strategy *doesn’t work* (and hence, isn’t worth discussing). As said, Oracle’s JVM already *has* a limit for stack traces (1024), so a counting solution will be compatible with that if the counter is capable of holding a number up to that, which is not too far fetched. If the recursion goes deeper, you’ll lose the capability to report the exact number, but this doesn’t differ to today’s JVM. – Holger Dec 04 '15 at 08:38
  • *"Once you confront one of these constraints with reality, this strategy doesn’t work"* That is exactly the point I'm making. We appear to now be violently agreeing. My first comment was *"I'm not much of a fan of hypothetical situations based on infinity. They have a tendency to trick you into thinking something is possible for finite situations which really isn't."* – Philip Couling Dec 04 '15 at 09:46
0

I have done some modification to your code and have done some tests. I still cannot figure out an answer to your question. It is the finally block for sure that causes the whole wearied behavior of the code.

public class SimpleFile {

    static int i = 0;

    public static void main(String[] args) {
        int c = i++;
        System.out.println("main" + i);

        try {
            SimpleFile.main(args);
        }

        catch (Throwable e) {
            System.out.println("Catch" + e);
        }

        finally {
            if (i < 30945) {
                System.out.println("finally" + c);
                SimpleFile.main(args);
            }
        }
    }
}

And the out put is.. i am only showing the last lines:

main30941
main30942finally30940
main30943finally30927
main30944
main30945
main30946
main30947Catchjava.lang.StackOverflowError

I want to prove that even static methods do get StackOverflowError if called recursively. And since you were catching exception that would never catch an Error.

See Call Main Recursively question for StackOverflowError if main is called recursively

Community
  • 1
  • 1
awsome
  • 2,143
  • 2
  • 23
  • 41
-1

Static methods are permanent, it is not stored in a stack but, in a heap instead. The code code you wrote simply calls the same code over and over from the heap, so it won't throw StackOverFlowError. Also, the string inside System.out.println("main"); is stored in the same location which is permanent. Even though you called the code over and over, the same object of string is used, it won't fill the stack.

I got this explanation from the following link :

http://www.oracle.com/technetwork/java/javase/memleaks-137499.html#gbyuu

3.1.2 Detail Message: PermGen space The detail message PermGen space indicates that the permanent generation is full. The permanent generation is the area of the heap where class and method objects are stored. If an application loads a very large number of classes, then the size of the permanent generation might need to be increased using the -XX:MaxPermSize option.

Interned java.lang.String objects are also stored in the permanent generation. The java.lang.String class maintains a pool of strings. When the intern method is invoked, the method checks the pool to see if an equal string is already in the pool. If there is, then the intern method returns it; otherwise it adds the string to the pool. In more precise terms, the java.lang.String.intern method is used to obtain the canonical representation of the string; the result is a reference to the same class instance that would be returned if that string appeared as a literal. If an application interns a huge number of strings, the permanent generation might need to be increased from its default setting.

When this kind of error occurs, the text String.intern or ClassLoader.defineClass might appear near the top of the stack trace that is printed.

The jmap -permgen command prints statistics for the objects in the permanent generation, including information about internalized String instances.

Logos
  • 334
  • 1
  • 7
  • 17
  • 2
    it is not true that static methods don't throw StackOverFlowError – awsome Dec 03 '15 at 09:01
  • 1
    You are getting confused between the memory used to store program code and the memory used to store "stack frames". Regardless of whether a method is static or not, calls to it must generate stack frames otherwise java will loose track of how many times it has recursed in. – Philip Couling Dec 03 '15 at 10:33
  • @couling: in case of a tail-call optimization, the number of recursive invocations may indeed get lost, but Oracle’s JVM doesn’t perform this kind of optimization. Still, the number of possible recursions depends on the optimization state of a method, but you’re right, this has nothing to do with the method being `static` or not. – Holger Dec 03 '15 at 10:37