Why does this method print 4?

I think the others have done a good job at explaining why cnt > 0, but there's not enough details regarding why cnt = 4, and why cnt varies so widely among different settings. I will attempt to fill that void here.

Let

  • X be the total stack size
  • M be the stack space used when we enter main the first time
  • R be the stack space increase each time we enter into main
  • P be the stack space necessary to run System.out.println

When we first get into main, the space left over is X-M. Each recursive call takes up R more memory. So for 1 recursive call (1 more than original), the memory use is M + R. Suppose that StackOverflowError is thrown after C successful recursive calls, that is, M + C * R <= X and M + C * (R + 1) > X. At the time of the first StackOverflowError, there's X - M - C * R memory left.

To be able to run System.out.prinln, we need P amount of space left on the stack. If it so happens that X - M - C * R >= P, then 0 will be printed. If P requires more space, then we remove frames from the stack, gaining R memory at the cost of cnt++.

When println is finally able to run, X - M - (C - cnt) * R >= P. So if P is large for a particular system, then cnt will be large.

Let's look at this with some examples.

Example 1: Suppose

  • X = 100
  • M = 1
  • R = 2
  • P = 1

Then C = floor((X-M)/R) = 49, and cnt = ceiling((P - (X - M - C*R))/R) = 0.

Example 2: Suppose that

  • X = 100
  • M = 1
  • R = 5
  • P = 12

Then C = 19, and cnt = 2.

Example 3: Suppose that

  • X = 101
  • M = 1
  • R = 5
  • P = 12

Then C = 20, and cnt = 3.

Example 4: Suppose that

  • X = 101
  • M = 2
  • R = 5
  • P = 12

Then C = 19, and cnt = 2.

Thus, we see that both the system (M, R, and P) and the stack size (X) affects cnt.

As a side note, it does not matter how much space catch requires to start. As long as there is not enough space for catch, then cnt will not increase, so there are no external effects.

EDIT

I take back what I said about catch. It does play a role. Suppose it requires T amount of space to start. cnt starts to increment when the leftover space is greater than T, and println runs when the leftover space is greater than T + P. This adds an extra step to the calculations and further muddies up the already muddy analysis.

EDIT

I finally found time to run some experiments to back up my theory. Unfortunately, the theory doesn't seem to match up with the experiments. What actually happens is very different.

Experiment setup: Ubuntu 12.04 server with default java and default-jdk. Xss starting at 70,000 at 1 byte increments to 460,000.

The results are available at: https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM I've created another version where every repeated data point is removed. In other words, only points that are different from the previous are shown. This makes it easier to see anomalies. https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA


This is the victim of bad recursive call. As you are wondering why the value of cnt varies, it is because the stack size depends on the platform. Java SE 6 on Windows has a default stack size of 320k in the 32-bit VM and 1024k in the 64-bit VM. You can read more here.

You can run using different stack sizes and you will see different values of cnt before the stack overflows-

java -Xss1024k RandomNumberGenerator

You don't see the value of cnt being printed multiple times even though the value is greater than 1 sometimes because your print statement is also throwing error which you can debug to be sure through Eclipse or other IDEs.

You can change the code to the following to debug per statement execution if you'd prefer-

static int cnt = 0;

public static void main(String[] args) {                  

    try {     

        main(args);   

    } catch (Throwable ignore) {

        cnt++;

        try { 

            System.out.println(cnt);

        } catch (Throwable t) {   

        }        
    }        
}

UPDATE:

As this getting a lot more attention, let's have another example to make things clearer-

static int cnt = 0;

public static void overflow(){

    try {     

      overflow();     

    } catch (Throwable t) {

      cnt++;                      

    }

}

public static void main(String[] args) {

    overflow();
    System.out.println(cnt);

}

We created another method named overflow to do a bad recursion and removed the println statement from the catch block so it doesn't start throwing another set of errors while trying to print. This works as expected. You can try putting System.out.println(cnt); statement after cnt++ above and compile. Then run multiple times. Depending on your platform, you may get different values of cnt.

This is why generally we do not catch errors because mystery in code is not fantasy.


The behavior is dependent upon the stack size (which can be manually set using Xss. The stack size is architecture specific. From JDK 7 source code:

// Default stack size on Windows is determined by the executable (java.exe
// has a default value of 320K/1MB [32bit/64bit]). Depending on Windows version, changing
// ThreadStackSize to non-zero may have significant impact on memory usage.
// See comments in os_windows.cpp.

So when the StackOverflowError is thrown, the error is caught in catch block. Here println() is another stack call which throws exception again. This gets repeated.

How many times it repeates? - Well it depends on when JVM thinks it is no longer stackoverflow. And that depends on the stack size of each function call (difficult to find) and the Xss. As mentioned above default total size and size of each function call (depends on memory page size etc) is platform specific. Hence different behavior.

Calling the java call with -Xss 4M gives me 41. Hence the correlataion.


I think the number displayed is the number of time the System.out.println call throws the Stackoverflow exception.

It probably depend on the implementation of the println and the number of stacking call it is made in it.

As an illustration:

The main() call trigger the Stackoverflow exception at call i. The i-1 call of main catch the exception and call println which trigger a second Stackoverflow. cnt get increment to 1. The i-2 call of main catch now the exception and call println. In println a method is called triggering a 3rd exception. cnt get increment to 2. this continue until println can make all its needed call and finally display the value of cnt.

This is then dependent of the actual implementation of println.

For the JDK7 either it detect cycling call and throws the exception earlier either it keep some stack resource and throw the exception before reaching the limit to give some room for remediation logic either the println implementation doesn't make calls either the ++ operation is done after the println call thus is by pass by the exception.