2

I met the following problem in a Java exam, why following recursively calling a function can run forever even though StackOverflowError?

public class Solution {
    static int i = 0;

    public static void f(){
        System.out.println(i++);
        try {
            f();
        } catch (StackOverflowError e) {
            System.out.println(e);
            f();
        }
    }

    public static void main(String[] args) {
        f();
    }
}

I cannot understand why JVM can still run when I've exhausted all call stack memory? Is there any reference, like JVM specification or JVM source code, can explain above phenomenon?

maplemaple
  • 1,297
  • 6
  • 24
  • 4
    It runs forever because you catch the stackoverflowerror exception and call the function again. In effect, it's an inifinite loop. – President James K. Polk Mar 14 '22 at 01:51
  • @PresidentJamesK.Polk Why? Since I've exhaust the call stack memory, how can JVM still run? – maplemaple Mar 14 '22 at 01:52
  • 2
    It's not an infinite loop, but it will take a very long time to terminate. Each time an `f()` call throws a `StackOverflowError`, you catch it and make another `f()` call that is not in a `try` block. Eventually that call will also throw a `StackOverflowError`, and then the current call is finished. If the stack can reach a maximum depth of `d`, then `f()` will be called approximately `2^d` times before the whole program terminates. Since `d` is likely on the order of a few hundred to a few thousand, it will not terminate in our lifetimes, but it is not theoretically infinite. – kaya3 Mar 14 '22 at 01:57
  • *"Since I've exhaust the call stack memory, how can JVM still run?"* So your question is, how is it even possible to catch a `StackOverflowError` and recover from it at all? That's a much different question to why this function takes "forever" to run. – kaya3 Mar 14 '22 at 01:58
  • @kaya3 Yes, I want to understand this phenomena. – maplemaple Mar 14 '22 at 02:06
  • @kaya3 Is there any reference for his saying? Like JVM Specification? – maplemaple Mar 14 '22 at 02:09
  • https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.3 – President James K. Polk Mar 14 '22 at 02:11
  • You haven't exhausted memory. You have only exhausted this thread's stack. – user207421 Mar 14 '22 at 02:38
  • 1
    To second @kaya3, we can insert an artificial limit on the stack depth, throwing the `StackOverflowError` manually when a certain limit has been reached and experiment with that limit, see https://ideone.com/tTZFkP It clearly demonstrates the nonlinear nature of the problem. With the actual JVM’s stack size, it’s still finite in theory but takes way too long for a human to perceive the end. – Holger Mar 14 '22 at 08:30

1 Answers1

1

I'm neither java nor jvm expert, but I think adding a extra recursive depth output can show exactly what is going on with the call stack.

public class Solution {
  static int i = 0;

  public static void f(int depth) {
    System.out.println(i++);
    System.out.println(String.format("depth=%d", ++depth));
    try {
      f(depth);
    } catch (StackOverflowError e) {
      System.out.println(e);
      f(depth);
    }
  }

  public static void main(String[] args) {
    f(1);
  }
}

In my test, typical output is

...more
java.lang.StackOverflowError
161735
depth=3745
161736
depth=3746
161737
java.lang.StackOverflowError
161738
java.lang.StackOverflowError
161739
java.lang.StackOverflowError
161740
depth=3744
161741
depth=3745
...more

StackOverflowError is thrown because jvm is considering a size limit(1M by default?) of stack memory has reached, but the exception is caught, so the recursion is not stopped, it still tries to call, jvm throw away the recent 1 or 2 problematic calls, and then try continue, so the depth value goes around the same maximum value again and again.

I think this way the OP can understand why i is increasing without any problem. I've exhaust the call stack memory -- you haven't, you nearly exhaust, but jvm always cleared latest few calls to avoid exhaust.

Holger
  • 285,553
  • 42
  • 434
  • 765
Lei Yang
  • 3,970
  • 6
  • 38
  • 59