Try-finally impedisce StackOverflowError

Dare un’occhiata a uno dei due metodi seguenti:

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

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

Esecuzione bar() chiaramente i risultati in un StackOverflowError, ma l’esecuzione di foo() non (il programma sembra funzionare a tempo indeterminato). Perché è che?

  • La JVM non consentono di coda di chiamata di ottimizzazione? La conversione la ricorsione infinita in un ciclo infinito.
  • Formalmente, il programma si arresta a causa di errori gettati durante l’elaborazione del finally clausola si propagano a un livello superiore. Ma non trattenete il respiro; il numero di passi compiuti, sarà di circa 2 a (massima profondità dello stack) e il lancio di eccezioni non è esattamente a buon mercato, sia.
  • Mentre potrebbe consentire, cosa ti fa pensare che è una corretta ottimizzazione in questo caso? (Suggerimento: non è la fine del finally blocco non è gratis).
  • Sarebbe “corretto” per bar(), però.
  • Java non TCO, IIRC per assicurarsi di avere piena tracce dello stack, e per qualcosa legato alla riflessione (probabilmente hanno a che fare con le tracce dello stack così).
  • È interessante notare che quando ho provato questo fuori .Net (utilizzo di Mono), il programma si è schiantato con un StackOverflow errore senza mai chiamare finalmente.
  • diversi libreria di runtime del tutto. Io sono al 99% che in .NET è possibile prendere overflow dello stack.
  • Che design. Da StackOverflowException: “Partire con il .NET Framework versione 2.0, un StackOverflowException oggetto non può essere catturato da un blocco try-catch e il relativo processo è terminato per impostazione predefinita.” In .NET 1.1 credo che si possa ottenere lo stesso comportamento come Java.
  • questo è uno dei puzzle in Java™ Puzzlers: Traps, Pitfalls, and Corner Cases libro
  • Questo è il peggior pezzo di codice che ho mai visto 🙂

InformationsquelleAutor arshajii | 2012-09-15

 

6 Replies
  1. 328

    Non funzionare per sempre. Ogni stack overflow causa il codice per spostare il blocco finally. Il problema è che ci vorrà davvero un lungo periodo di tempo. Ordine di tempo è O(2^N), dove N è la massima profondità dello stack.

    Immaginare la profondità massima è di 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()

    Di lavoro di ogni livello nel blocco finally, prendere il doppio del tempo di una pila di profondità potrebbe essere
    10.000 o più. Se si può fare i 10.000.000 di chiamate al secondo, questo avrà 10^3003 secondi o più a lungo rispetto all’età dell’universo.

    • Si può mostrare un albero per una profondità massima di 5?
    • Bello, anche se cerco di fare lo stack più piccolo possibile via -Xss, ho una profondità di [150 – 210], quindi 2^n finisce per essere un [47 – 65] numero di cifre. Non ha intenzione di aspettare così a lungo, che è abbastanza vicino all’infinito per me.
    • Solo per voi, ho aumentato la profondità di 5. 😉
    • Così, alla fine della giornata, quando foo infine terminare, si tradurrà in un StackOverflowError?
    • a seguito della matematica, yup. l’ultimo stack overflow da ultimo, infine, che non è riuscito a stack overflow uscita con… overflow dello stack =P. riuscivo a resistere.
    • non c’è dubbio. Ho postato una versione sintetizzata di questa discarica verso la fine di questa pagina domanda in un’altra risposta, ma c’è solo per il supporto di Peter punto. Se si desidera votare, io chiedo di votare la sua risposta invece.
    • Quindi questo vuol dire anche che provare a prendere anche il codice dovrebbe finire in stackoverflow errore??
    • La sua infinita per me, ho 10528 pippo chiama, poi “finalmente” riporta all’10524, poi di nuovo 10528, poi di nuovo 10524 & ecc…. Molto interessante comunque.
    • Che non dovrebbe essere il caso, vorrei controllare i tuoi numeri.
    • Come mostrato qui, la profondità di ricorsione dipende fortemente lo stato di compilazione/ottimizzazione, e che per un metodo che fa qualcosa (conteggio). L’impatto di ottimizzazione può essere anche superiore ad un metodo che è in realtà un non-op (oltre la ricorsione). Dal momento che l’esecuzione, come se avere un’infinita pila legali in esecuzione, è possibile che il codice ottimizzato funziona sempre. O viene eseguito molto più tempo di quello previsto.
    • mentre non infinito, il tempo di esecuzione può essere più lungo rispetto all’età dell’universo, o il tempo che la terra ha lasciato.
    • e in esecuzione fino a quando il computer rimanenti vita è già abbastanza per fare la differenza irrilevante…

  2. 40

    Quando si riceve un’eccezione dalla chiamata di foo() all’interno del try, si chiama foo() da finally e iniziare a recursing di nuovo. Quando che causa un’altra eccezione, puoi chiamare foo() da un altro interno finally(), e così via quasi all’infinito.

    • Presumibilmente, un StackOverflowError (SOE) viene inviato quando non c’è più spazio sullo stack per chiamare metodi nuovi. Come si può foo() essere chiamato da infine dopo una parte di SOE?
    • leggi su finally semantica.
    • se non c’è abbastanza spazio, è di ritorno dall’ultima foo() invocazione, e richiamare foo() in finally blocco della corrente foo() invocazione.
    • +1 per ninjalj. Non si chiameranno pippo da ovunque una volta che non è possibile chiamare pippo a causa della condizione di overflow. questo include, infine, il blocco, che è il motivo per cui questo sarà, alla fine (l’età dell’universo) risolvere.
  3. 38

    Provare a eseguire il codice riportato di seguito:

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

    Troverete che il blocco finally viene eseguito prima di lanciare un’Eccezione al livello sopra di esso. (Uscita:

    Infine

    Exception in thread “main” java.lang.Eccezione: per i TEST!
    al test.principale(il test.java:6)

    Questo senso, in quanto, infine, è chiamato a destra prima di uscire il metodo. Ciò significa, tuttavia, che una volta che si ottiene che il primo StackOverflowError, prova a buttare, ma che alla fine deve eseguire per primo, in modo che venga eseguito foo() di nuovo, che ottiene un altro stack overflow, e come tale viene eseguito alla fine di nuovo. Questo è già accaduto per sempre, quindi, l’eccezione non è effettivamente stampato.

    Nella vostra barra metodo tuttavia, non appena si verifica l’eccezione, è appena gettato dritto fino al livello di cui sopra, e sarà stampato

    • Downvote. “è già accaduto per sempre” è sbagliato. Visualizzare altre risposte.
  4. 26

    In uno sforzo per fornire una ragionevole evidenza che questo finirà, io offro il seguente insensato codice. Nota: Java NON è il mio linguaggio, da qualsiasi tratto della più fervida immaginazione. Enuncio questo solo per sostenere la risposta di Pietro, che è il risposta corretta alla domanda.

    In questo modo si tenta di simulare le condizioni di ciò che accade quando una chiamata NON può accadere, perché sarebbe introdurre un overflow dello stack. Mi sembra la cosa più difficile che le persone non riescono a cogliere in che la chiamata non avviene quando si non accadere.

    public class Main
    {
        public static void main(String[] args)
        {
            try
            {   //invoke foo() with a simulated call depth
                Main.foo(1,5);
            }
            catch(Exception ex)
            {
                System.out.println(ex.toString());
            }
        }
    
        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("[email protected]("+n+")");
            }
            finally
            {
                System.out.println(n + " - Finally");
                if (n < limit)
                    foo(n+1,limit);
                else
                    throw new Exception("[email protected]("+n+")");
            }
        }
    }

    L’uscita di questo po ‘ inutile mucchio di goo è il seguente, e l’effettiva rilevata eccezione può venire come una sorpresa; Oh, e 32 prova-chiama (2^5), che è del tutto previsto:

    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: [email protected](5)
  5. 23

    Imparare a tracciare il programma:

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

    Questo è il risultato che vedo:

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

    Come si può vedere StackOverFlow è gettato a strati sopra, così si può fare un ulteriore ricorsione passi fino a colpire un’altra eccezione, e così via. Questo è un infinito “loop”.

    • non è in realtà in loop infinito, se hai la pazienza finirà per terminare. Non posso trattenere il respiro per se.
    • Ipotizzerei che è infinita. Ogni volta che si raggiunge la massima profondità dello stack viene generata un’eccezione e si snoda stack. Tuttavia nel infine chiama Pippo di nuovo causando di nuovo riutilizzare lo spazio dello stack che ha appena recuperato. Si andrà avanti e indietro la generazione di eccezioni e poi andando avanti Dow stack fino a quando non succede di nuovo. Per sempre.
    • Inoltre, è necessario che prima il sistema.out.println essere in dichiarazione di prova, altrimenti rilassarsi il ciclo di più di quanto non dovrebbe. possibilmente facendolo fermare.
    • Il problema con il tuo argomento è che quando chiama foo la seconda volta, nel finally blocco, esso non è più in una try. Così, mentre si torna indietro lungo la pila e a creare più di overflow dello stack una volta, la seconda volta si tratterà solo di rigenerare l’errore prodotto dalla seconda chiamata a foo, invece di ri-approfondimento.
  6. 0

    Il programma semplicemente sembra funzionare per sempre, che termina, ma ci vuole esponenzialmente più tempo e più spazio per lo stack che hai. Per dimostrare che finisce, ho scritto un programma che prima si esaurisce più spazio di stack, e quindi chiama foo, e, infine, scrive una traccia di quello che è successo:

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

    Codice:

    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; //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);
          }
        });
      }
    }

    È possibile provare on-line! (Alcune esecuzioni potrebbe chiamare foo più o meno volte rispetto agli altri,

Lascia un commento