Le bloc Try-finally empêche StackOverflowError

Jetez un coup d’œil aux deux méthodes suivantes:

public static void foo() { try { foo(); } finally { foo(); } } public static void bar() { bar(); } 

Exécuter bar() entraîne clairement une StackOverflowError , mais l’exécution de foo() ne le fait pas (le programme semble fonctionner indéfiniment). Pourquoi donc?

Il ne fonctionne pas pour toujours. Chaque dépassement de stack provoque le déplacement du code vers le bloc finally. Le problème est que cela prendra vraiment très longtemps. L’ordre temporel est O (2 ^ N) où N est la profondeur maximale de la stack.

Imaginez que la profondeur maximale est de 5

 foo() calls foo() calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() 

Travailler chaque niveau dans le bloc final prend deux fois plus de temps et la profondeur de la stack peut être de 10 000 ou plus. Si vous pouvez faire 10 000 000 d’appels par seconde, cela prendra 10 ^ 3003 secondes ou plus que l’âge de l’univers.

Lorsque vous obtenez une exception de l’invocation de foo() à l’intérieur de l’ try , vous appelez foo() partir de finally et vous recommencez à récurer. Lorsque cela provoque une autre exception, vous appelez foo() depuis un autre interne, finally() et ainsi de suite, presque à l’ infini) .

Essayez d’exécuter le code suivant:

  try { throw new Exception("TEST!"); } finally { System.out.println("Finally"); } 

Vous constaterez que le bloc finally s’exécute avant de lancer une exception jusqu’au niveau supérieur. (Sortie:

finalement

Exception dans le thread “main” java.lang.Exception: TEST! à test.main (test.java:6)

Cela a du sens, comme on l’appelle enfin juste avant de quitter la méthode. Cela signifie, cependant, qu’une fois que vous avez obtenu ce premier StackOverflowError , il essaiera de le lancer, mais finalement, il devra être exécuté en premier, donc il exécutera à nouveau foo() , ce qui provoquera un autre débordement de stack et sera finalement exécuté à nouveau. Cela continue à arriver pour toujours, donc l’exception n’est jamais réellement imprimée.

Dans votre méthode de barre cependant, dès que l’exception se produit, elle est juste jetée directement au niveau supérieur et sera imprimée

Dans le but de fournir des preuves raisonnables que cela va éventuellement se terminer, je propose le code suivant, plutôt dénué de sens. Note: Java n’est pas ma langue, par une partie de l’imagination la plus vive. Je ne présente cela que pour appuyer la réponse de Peter, qui est la bonne réponse à la question.

Cela tente de simuler les conditions de ce qui se produit quand un appel ne peut pas se produire car cela introduirait un débordement de stack. Il me semble que la chose la plus difficile à saisir pour les gens est que l’invocation ne se produit pas lorsque cela ne peut arriver.

 public class Main { public static void main(Ssortingng[] args) { try { // invoke foo() with a simulated call depth Main.foo(1,5); } catch(Exception ex) { System.out.println(ex.toSsortingng()); } } public static void foo(int n, int limit) throws Exception { try { // simulate a depth limited call stack System.out.println(n + " - Try"); if (n < limit) foo(n+1,limit); else throw new Exception("StackOverflow@try("+n+")"); } finally { System.out.println(n + " - Finally"); if (n < limit) foo(n+1,limit); else throw new Exception("StackOverflow@finally("+n+")"); } } } 

La sortie de ce petit tas de goo inutile est la suivante, et l'exception réelle capturée peut être une surprise; Oh, et 32 ​​try-calls (2 ^ 5), ce qui est tout à fait attendu:

 1 - Try 2 - Try 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 2 - Finally 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 1 - Finally 2 - Try 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 2 - Finally 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally java.lang.Exception: StackOverflow@finally(5) 

Apprenez à suivre votre programme:

 public static void foo(int x) { System.out.println("foo " + x); try { foo(x+1); } finally { System.out.println("Finally " + x); foo(x+1); } } 

C’est la sortie que je vois:

 [...] foo 3439 foo 3440 foo 3441 foo 3442 foo 3443 foo 3444 Finally 3443 foo 3444 Finally 3442 foo 3443 foo 3444 Finally 3443 foo 3444 Finally 3441 foo 3442 foo 3443 foo 3444 [...] 

Comme vous pouvez le voir, StackOverFlow est lancé sur certaines couches ci-dessus, vous pouvez donc effectuer des étapes de récursion supplémentaires jusqu’à ce que vous atteigniez une autre exception, etc. C’est une “boucle” infinie.

Le programme semble simplement fonctionner pour toujours; cela prend fin, mais cela prend plus de temps de manière exponentielle, plus vous avez d’espace de stack. Pour prouver qu’il finit, j’ai écrit un programme qui épuise d’abord la majeure partie de l’espace de stack disponible, puis appelle foo , et écrit enfin une trace de ce qui s’est passé:

 foo 1 foo 2 foo 3 Finally 3 Finally 2 foo 3 Finally 3 Finally 1 foo 2 foo 3 Finally 3 Finally 2 foo 3 Finally 3 Exception in thread "main" java.lang.StackOverflowError at Main.foo(Main.java:39) at Main.foo(Main.java:45) at Main.foo(Main.java:45) at Main.foo(Main.java:45) at Main.consumeAlmostAllStack(Main.java:26) at Main.consumeAlmostAllStack(Main.java:21) at Main.consumeAlmostAllStack(Main.java:21) ... 

Le code:

 import java.util.Arrays; import java.util.Collections; public class Main { static int[] orderOfOperations = new int[2048]; static int operationsCount = 0; static StackOverflowError fooKiller; static Error wontReachHere = new Error("Won't reach here"); static RuntimeException done = new RuntimeException(); public static void main(Ssortingng[] args) { try { consumeAlmostAllStack(); } catch (RuntimeException e) { if (e != done) throw wontReachHere; printResults(); throw fooKiller; } throw wontReachHere; } public static int consumeAlmostAllStack() { try { int stackDepthRemaining = consumeAlmostAllStack(); if (stackDepthRemaining < 9) { return stackDepthRemaining + 1; } else { try { foo(1); throw wontReachHere; } catch (StackOverflowError e) { fooKiller = e; throw done; //not enough stack space to construct a new exception } } } catch (StackOverflowError e) { return 0; } } public static void foo(int depth) { //System.out.println("foo " + depth); Not enough stack space to do this... orderOfOperations[operationsCount++] = depth; try { foo(depth + 1); } finally { //System.out.println("Finally " + depth); orderOfOperations[operationsCount++] = -depth; foo(depth + 1); } throw wontReachHere; } public static String indent(int depth) { return String.join("", Collections.nCopies(depth, " ")); } public static void printResults() { Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> { if (depth > 0) { System.out.println(indent(depth - 1) + "foo " + depth); } else { System.out.println(indent(-depth - 1) + "Finally " + -depth); } }); } } 

Vous pouvez l’ essayer en ligne! (Certaines courses peuvent appeler plus ou moins de fois que d’autres)