Das Programm scheint nur für immer zu laufen; es endet tatsächlich, benötigt jedoch exponentiell mehr Zeit, je mehr Stapelspeicher vorhanden ist. Um zu zeigen, dass es endet, habe ich ein Programm geschrieben, das zuerst den Großteil des verfügbaren Stapelspeichers verbraucht, dann foo
aufruft und schließlich eine Spur dessen schreibt, was passiert ist:
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)
...
Der 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(String[] 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; //nicht genügend Stapelspeicher, um eine neue Ausnahme zu erzeugen
}
}
} catch (StackOverflowError e) {
return 0;
}
}
public static void foo(int depth) {
//System.out.println("foo " + depth); Nicht genügend Stapelspeicher, um dies zu tun...
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);
}
});
}
}
Sie können es online ausprobieren! (Einige Durchläufe könnten foo
öfter oder seltener aufrufen)
0 Stimmen
Erlaubt die JVM keine Optimierung von Endaufrufen? Das würde die endlose Rekursion in eine endlose Schleife umwandeln.
17 Stimmen
Formell wird das Programm letztendlich stoppen, da Fehler, die während der Verarbeitung der
finally
Klausel auftreten, an die nächste höhere Ebene weitergeleitet werden. Aber halten Sie nicht den Atem an; die Anzahl der Schritte beträgt etwa 2 zur (maximalen Stapeltiefe) und das Werfen von Ausnahmen ist auch nicht gerade billig.0 Stimmen
@dan04 Während es dies ermöglichen könnte, was lässt dich glauben, dass es sich um eine korrekte Optimierung handelt? (Hinweis: Das ist es nicht, da das Ende des
finally
Blocks nicht frei ist.)3 Stimmen
Es wäre jedoch "korrekt" für
bar()
.6 Stimmen
@dan04: Java führt keine TCO durch, soweit ich mich erinnere, um sicherzustellen, dass vollständige Stapelverfolgungen vorliegen, und für etwas im Zusammenhang mit Reflektion (wahrscheinlich auch mit Stapelverfolgungen).
4 Stimmen
Interessanterweise stürzte das Programm beim Test auf .Net (mit Mono) mit einem StackOverflow-Fehler ab, ohne jemals finally aufzurufen.
0 Stimmen
@Kibbee eine völlig andere Laufzeitbibliothek. Ich bin mir zu 99% sicher, dass man bei .NET Stacküberläufe nicht abfangen kann.
2 Stimmen
@Kibbee Das ist beabsichtigt. Von StackOverflowException: "Beginnend mit dem .NET Framework Version 2.0 kann ein StackOverflowException-Objekt nicht von einem try-catch-Block eingefangen werden und der entsprechende Prozess wird standardmäßig beendet." Ich glaube, dass Sie im .NET 1.1 dasselbe Verhalten wie in Java erhalten würden.
1 Stimmen
Dies ist eines der Rätsel im Buch
Java™ Puzzlers: Traps, Pitfalls, and Corner Cases
11 Stimmen
Das ist das schlechteste Stück Code, das ich je gesehen habe :)