334 Stimmen

Versuchen-Schließlich-Block verhindert StackOverflowError

Schauen Sie sich die folgenden beiden Methoden an:

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

public static void bar() {
    bar();
}

Das Ausführen von bar() führt offensichtlich zu einem StackOverflowError, aber das Ausführen von foo() nicht (das Programm scheint einfach endlos zu laufen). Warum ist das so?

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.)

333voto

Peter Lawrey Punkte 511323

Es läuft nicht für immer. Jedes StackOverflow führt dazu, dass der Code zum finally-Block wechselt. Das Problem ist, dass es eine wirklich, wirklich lange Zeit dauern wird. Die Zeitkomplexität beträgt O(2^N), wobei N die maximale Stacktiefe ist.

Stellen Sie sich vor, die maximale Tiefe beträgt 5

foo() ruft auf
    foo() ruft auf
       foo() ruft auf
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
       schließlich
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
    ruft schließlich auf
       foo() ruft auf
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
       schließlich
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
ruft schließlich auf
    foo() ruft auf
       foo() ruft auf
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
       schließlich
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
    ruft schließlich auf
       foo() ruft auf
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen
       schließlich
           foo() ruft auf
              foo() auf, das es nicht schafft, foo() aufzurufen
           ruft schließlich auf
              foo() auf, das es nicht schafft, foo() aufzurufen

Um jedes Level in den finally-Block zu bringen, dauert doppelt so lange wie die Stacktiefe und könnte 10.000 oder mehr betragen. Wenn Sie pro Sekunde 10.000.000 Aufrufe machen können, dauert es 10^3003 Sekunden oder länger als das Alter des Universums.

40voto

ninjalj Punkte 40810

Wenn Sie eine Ausnahme von der Invocation von foo() innerhalb des try erhalten, rufen Sie foo() von finally auf und beginnen erneut mit der Rekursion. Wenn dadurch eine weitere Ausnahme verursacht wird, rufen Sie foo() von einem anderen inneren finally() auf, und so weiter fast ad infinitum.

38voto

Alex Coleman Punkte 7046

Versuchen Sie, den folgenden Code auszuführen:

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

Sie werden feststellen, dass der finally-Block ausgeführt wird, bevor eine Exception auf die darüberliegende Ebene geworfen wird. (Ausgabe:

Finally

Exception in thread "main" java.lang.Exception: TEST! at test.main(test.java:6)

Dies macht Sinn, da finally direkt vor dem Verlassen der Methode aufgerufen wird. Das bedeutet jedoch, dass einmal die erste StackOverflowError auftritt, sie versuchen wird, sie zu werfen, aber das finally muss zuerst ausgeführt werden, also führt es foo() erneut aus, was zu einem weiteren Stack Overflow führt, und so führt es das finally erneut aus. Dies geschieht endlos, daher wird die Exception nie tatsächlich gedruckt.

In Ihrer bar-Methode hingegen, sobald die Exception auftritt, wird sie direkt auf die darüberliegende Ebene geworfen und gedruckt.

26voto

WhozCraig Punkte 63618

In dem Bestreben, vernünftige Beweise dafür zu liefern, dass dies letztendlich enden wird, biete ich den folgenden eher bedeutungslosen Code an. Hinweis: Java ist NICHT meine Sprache, in keiner Weise, selbst in der lebhaftesten Vorstellungskraft. Ich offeriere dies nur, um Peters Antwort zu unterstützen, die die richtige Antwort auf die Frage ist.

Das versucht, die Bedingungen zu simulieren, die auftreten, wenn ein Aufruf nicht erfolgen kann, weil dadurch ein Stapelüberlauf eingeführt würde. Es scheint mir das Schwierigste zu sein, was die Leute nicht begreifen, nämlich dass der Aufruf nicht erfolgt, wenn es nicht möglich ist.

public class Main
{
    public static void main(String[] args)
    {
        try
        {   // rufen Sie foo() mit einer simulierten Aufruftiefe auf
            Main.foo(1,5);
        }
        catch(Exception ex)
        {
            System.out.println(ex.toString());
        }
    }

    public static void foo(int n, int limit) throws Exception
    {
        try
        {   // simulieren Sie eine begrenzte Tiefenaufrufliste
            System.out.println(n + " - Versuch");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@try("+n+")");
        }
        finally
        {
            System.out.println(n + " - Abschließend");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@finally("+n+")");
        }
    }
}

Die Ausgabe dieses kleinen sinnlosen Haufens von Goo ist folgende, und die tatsächliche Ausnahme, die auftritt, mag eine Überraschung sein; Oh, und 32 Versuchsaufrufe (2^5), was völlig erwartet wird:

1 - Versuch
2 - Versuch
3 - Versuch
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
3 - Abschließend
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
2 - Abschließend
3 - Versuch
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
3 - Abschließend
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
1 - Abschließend
2 - Versuch
3 - Versuch
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
3 - Abschließend
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
2 - Abschließend
3 - Versuch
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
3 - Abschließend
4 - Versuch
5 - Versuch
5 - Abschließend
4 - Abschließend
5 - Versuch
5 - Abschließend
java.lang.Exception: StackOverflow@finally(5)

23voto

Karoly Horvath Punkte 91548

Learn to trace your program:

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

This is the output I see:

[...]
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
[...]

As you can see the StackOverFlow is thrown at some layers above, so you can do additional recursion steps till you hit another exception, and so on. This is an infinite "loop".

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X