2009-06-04 33 views
15

¿Cómo manejar StackOverflowError en Java?¿Cómo manejar StackOverflowError en Java?

+25

Crash. Eso es lo que siempre hago. –

+1

Por favor, publique el código que causa el desbordamiento de la pila. Evitar los desbordamientos de pila casi siempre es mejor que tratar de manejar la excepción. –

+8

Ahh, la ironía de esa pregunta en un sitio web con este nombre ... –

Respuesta

14

No estoy seguro de lo que quiere decir con "manejar".

Por supuesto que puede coger ese error:

public class Example { 
    public static void endless() { 
     endless(); 
    } 

    public static void main(String args[]) { 
     try { 
      endless(); 
     } catch(StackOverflowError t) { 
      // more general: catch(Error t) 
      // anything: catch(Throwable t) 
      System.out.println("Caught "+t); 
      t.printStackTrace(); 
     } 
     System.out.println("After the error..."); 
    } 
} 

pero que es muy probable que una mala idea, a menos que sepa exactamente lo que está haciendo.

+3

La excepción de manejo significa que desea continuar después de ignorar la excepción. Dado que es stackOverflowException, creo que no hay espacio de pila disponible que sea requerido por java. Entonces, ¿en caso de cómo proceder más? –

+4

Es muy probable que tenga un problema grave en su programa. Evalúa el trazado de la pila y comprueba si encuentras algo sospechoso. Puede continuar después de atrapar StackOverflowError porque la pila de llamadas se ha vaciado por el error arrojado ...Sin embargo, solo puedo repetirme: ¡encuentre el verdadero error que está causando el problema! – Huxi

+4

Ankur, es StachOverflowError, no StackOverflowException, con la diferencia de que el nombre "ERROR" indica que no se debe capturar a través de try ... catch, pero debe volver a escribir el código como yo y la mayoría de los otros sugieren. – Avrom

17

Probablemente tenga alguna recursión infinita sucediendo.

I.e. un método que llama a sí mismo una y otra

public void sillyMethod() 
{ 
    sillyMethod(); 
} 

Uno de manejar esto es para fijar su código para que la recursión termina en lugar de continuar para siempre.

+3

a veces la recursión infinita se disfraza muy bien. Mira el stacktrace dentro del depurador después del stackoverflow. –

+4

+1 para el nombre del método. Además: KnightsWhoSayNeet() – kevinarpe

1

Supongo que no puede, o al menos depende de la jvm que use. El desbordamiento de pila significa que no tiene espacio para almacenar variables locales y direcciones de retorno. Si tu jvm hace alguna forma de compilación, también tienes el stackoverflow en el jvm y eso significa que no puedes manejarlo o atraparlo. El jvm tiene que terminar.

Podría haber una manera de crear un jvm que permita ese comportamiento, pero sería lento.

No he probado el comportamiento con el jvm, pero en .net simplemente no puede manejar el stackoverflow. Incluso intentar capturar no ayudará. Como java y .net confían en el mismo concepto (máquinas virtuales con jit), sospecho que Java se comportaría de la misma manera. La presencia de una excepción de stackoverflow en .NET sugiere que puede haber alguna vm que permita al programa atraparla, aunque la normal no lo hace.

0

El seguimiento de la pila debe indicar la naturaleza del problema. Debería haber algún bucle obvio al leer el seguimiento de la pila.

Si no es un error, debe agregar un contador u otro mecanismo para detener la recursión antes de que la recursión sea tan profunda que provoque un desbordamiento de la pila.

Un ejemplo de esto podría ser si está manejando XML anidado en un modelo DOM con llamadas recursivas y el XML está anidado tan profundo que causa un desbordamiento de pila con sus llamadas anidadas (improbable, pero posible). Sin embargo, esto tendría que ser una anidación bastante profunda para causar un desbordamiento de la pila.

0

Como lo mencionan muchos en este hilo, la causa común de esto es una llamada al método recursivo que no finaliza. Siempre que sea posible, evite el desbordamiento de la pila y, si lo hace durante las pruebas, debería considerar esto en la mayoría de los casos como un error grave. En algunos casos, puede configurar el tamaño de la pila de subprocesos en Java para que sea más grande para manejar algunas circunstancias (grandes conjuntos de datos administrados en almacenamiento de pila local, llamadas recursivas largas) pero esto aumentará la huella de memoria general, lo que puede generar problemas en el número de subprocesos disponibles en la VM. En general, si obtienes esta excepción, el hilo y cualquier dato local a este hilo deberían considerarse tostados y no usados ​​(es decir, sospechosos y posiblemente corruptos).

13

Eche un vistazo a la publicación de Raymond ChenWhen debugging a stack overflow, you want to focus on the repeating recursive part. Un extracto:

If you go hunting through your defect tracking database trying to see whether this is a known issue or not, a search for the top functions on the stack is unlikely to find anything interesting. That's because stack overflows tend to happen at a random point in the recursion; each stack overflow looks superficially different from every other one even if they are the same stack overflow.

Suppose you're singing the song Frère Jacques, except that you sing each verse a few tones higher than the previous one. Eventually, you will reach the top of your singing range, and precisely where that happens depends on where your vocal limit lines up against the melody. In the melody, the first three notes are each a new "record high" (i.e., the notes are higher than any other note sung so far), and new record highs appear in the three notes of the third measure, and a final record high in the second note of the fifth measure.

If the melody represented a program's stack usage, a stack overflow could possibly occur at any of those five locations in the program's execution. In other words, the same underlying runaway recursion (musically represented by an ever-higher rendition of the melody) can manifest itself in five different ways. The "recursion" in this analogy was rather quick, just eight bars before the loop repeated. In real life, the loop can be quite long, leading to dozens of potential points where the stack overflow can manifest itself.

If you are faced with a stack overflow, then, you want to ignore the top of the stack, since that's just focusing on the specific note that exceeded your vocal range. You really want to find the entire melody, since that's what's common to all the stack overflows with the same root cause.

+0

enlace muy útil - eso es lo que aprendí a hacer en caso de un desbordamiento de la pila. –

7

Es posible que desee ver si la opción "-Xss" es compatible con su JVM.Si es así, puede intentar establecerlo en un valor de 512k (el valor predeterminado es 256k en Windows de 32 bits y Unix) y ver si eso hace cualquier cosa (que no sea hacer que se siente más tiempo hasta su StackOverflowException). Tenga en cuenta que esta es una configuración por subproceso, por lo que si tiene una gran cantidad de subprocesos en ejecución también es posible que desee aumentar su configuración de montón.

+1

+1, esta es la única respuesta que aborda la causa raíz del problema. Trabajo en un entorno empresarial con cientos de servicios, y muchos de ellos fallan intermitentemente, y no usan recursividad, simplemente hacen mucho. Los servicios son sólidos como una roca una vez que el tamaño de la pila se incrementa desde el valor predeterminado de 1 MB, a 4 MB o incluso a 16 MB. Y cuando ocurre un "StackOverflowError", no podemos necesariamente confiar en que aparezca en los registros, puede ser un ^% £ * 1 absoluto de un problema para rastrear. – Contango

0

simple,

Mire el seguimiento de pila que produce la StackOverflowError para que sepa dónde en el código que se produce y lo utilizan para encontrar la manera de volver a escribir el código para que no se llama a sí mismo de forma recursiva (la causa probable de su error) por lo que no volverá a suceder.

StackOverflowErrors no es algo que deba manejarse a través de una cláusula Try ... catch, sino que apunta a un error básico en la lógica de su código que debe ser arreglado por usted.

0

java.lang.Error javadoc:

Un error es una subclase de Throwable que indica serios problemas que una aplicación razonable no debe tratar de atrapar. La mayoría de esos errores son condiciones anormales. El error ThreadDeath, aunque es una condición "normal", también es una subclase de Error porque la mayoría de las aplicaciones no deberían intentar atraparlo. No se requiere un método para declarar en su cláusula throws cualquier subclase de error que pueda lanzarse durante la ejecución del método pero que no se capture, ya que estos errores son condiciones anormales que nunca deberían ocurrir.

Así que, no. Trate de encontrar lo que está mal en la lógica de su código. Esta excepción ocurre muy a menudo debido a la recursión infinita.

4

La respuesta correcta es la que ya se ha dado. Es probable que a) tenga un error en su código que conduzca a una recursión infinita que generalmente es bastante fácil de diagnosticar y corregir, ob) tenga un código que pueda conducir a recursiones muy profundas, por ejemplo, atravesando recursivamente un árbol binario desbalanceado. En esta última situación, debe modificar su código para no asignar la información en la pila (es decir, no repetir) sino asignarlo en el montón.

Por ejemplo, para un cruce de árbol desequilibrado, puede almacenar los nodos que necesitarán revisarse en una estructura de datos de pila. Para un recorrido en orden, recorrería las ramas izquierdas presionando cada nodo mientras lo visitaba hasta que tocara una hoja, la cual procesaría, luego sacaría un nodo de la parte superior de la pila, lo procesaría y luego reiniciaría su ciclo con la right child (simplemente configurando la variable de loop en el nodo derecho). Esto usará una cantidad constante de stack moviendo todo lo que estaba en la pila al montón en la estructura de datos Stack. Heap es típicamente mucho más abundante que stack.

Como suele ser una muy mala idea, pero es necesario en los casos en que el uso de la memoria es extremadamente limitado, puede utilizar la inversión del puntero. En esta técnica, codifica la pila en la estructura que está recorriendo, y al reutilizar los enlaces que está recorriendo, puede hacerlo sin o con mucha menos memoria adicional. Usando el ejemplo anterior, en lugar de presionar nodos cuando hacemos un bucle, solo necesitamos recordar a nuestro padre inmediato, y en cada iteración, establecemos el enlace que atravesamos al padre actual y luego el padre actual al nodo que estamos dejando. Cuando llegamos a una hoja, la procesamos, luego vamos a nuestro padre y luego tenemos un enigma. No sabemos si debemos corregir la rama de la izquierda, procesar este nodo y continuar con la rama de la derecha, o corregir la rama de la derecha e ir a nuestro elemento primario. Entonces, debemos asignar un poco más de información a medida que iteramos. Normalmente, para las realizaciones de bajo nivel de esta técnica, ese bit se almacenará en el puntero mismo, lo que no generará memoria adicional ni memoria constante en general. Esta no es una opción en Java, pero puede ser posible arrancar este bit en campos usados ​​para otras cosas.En el peor de los casos, esto sigue siendo al menos 32 o 64 veces la reducción en la cantidad de memoria necesaria. Por supuesto, este algoritmo es extremadamente fácil de equivocarse con resultados completamente confusos y causaría estragos en la concurrencia. Por lo tanto, casi nunca vale la pena la pesadilla de mantenimiento, excepto cuando asignar memoria es insostenible. El ejemplo típico es un recolector de basura donde algoritmos como este son comunes.

Lo que realmente quería hablar, sin embargo, es cuando es posible que desee manejar el StackOverflowError. A saber, para proporcionar eliminación de llamada final en la JVM. Un enfoque es usar un estilo de trampolín en el que, en lugar de realizar una llamada de cola, devuelva un objeto de procedimiento nullary, o si solo está devolviendo un valor, lo devuelve. [Nota: esto requiere algunos medios para decir que una función devuelve A o B. En Java, probablemente la forma más sencilla de hacer esto es devolver un tipo normalmente y arrojar el otro como una excepción.] Entonces cada vez que llamas a un método, Es necesario hacer un ciclo while llamando a los procedimientos nullary (que a su vez devolverán un procedimiento nullary o un valor) hasta que obtenga un valor. Un bucle sin fin se convertirá en un bucle while que está forzando constantemente objetos de procedimiento que devuelven objetos de procedimiento. Los beneficios del estilo de trampolín es que solo usa un factor constante más de la cantidad que se usaría con una implementación que eliminó correctamente todas las llamadas de cola, utiliza la pila de Java normal para llamadas sin cola, la traducción es simple, y solo crece el código por un factor constante (tedioso). El inconveniente es que asigna un objeto en cada llamada al método (que se convertirá inmediatamente en basura) y consumir estos objetos implica un par de llamadas indirectas por llamada final.

Lo ideal sería hacer nunca asignar esos procedimientos nullary o cualquier otra cosa, en primer lugar, que es exactamente lo que llevaría a cabo la eliminación llamada de cola. Sin embargo, al trabajar con lo que proporciona Java, lo que podríamos hacer es ejecutar el código de la forma habitual y solo hacer estos procedimientos nulary cuando nos quedemos sin pila. Ahora todavía asignamos esos marcos inútiles, pero lo hacemos en la pila en lugar de en el montón y desasignarlos a granel, también, nuestras llamadas son llamadas normales directas de Java. La forma más sencilla de describir esta transformación es reescribir primero todos los métodos de instrucciones de varias llamadas en métodos que tienen dos instrucciones de llamada, es decir, fgh() {f(); gramo(); h(); } se convierte en fgh() {f(); gh(); } y gh() {g(); h(); }. Para simplificar, supongo que todos los métodos terminan en una llamada final, que puede organizarse simplemente empacando el resto de un método en un método separado, aunque en la práctica, querría manejarlos directamente. Después de estas transformaciones tenemos tres casos, o bien un método tiene cero llamadas en cuyo caso no hay nada que hacer, o tiene una llamada (de cola), en cuyo caso lo envolvemos en un bloque try-catch del mismo modo que lo haremos para la llamada de cola en los dos casos de llamada. Por último, puede tener dos llamadas, una llamada no cola y una llamada de la cola, en cuyo caso se aplican las siguientes transformación ilustrado por ejemplo (utilizando C# 's lambda notación que podría ser fácilmente reemplazado con una clase interna anónima con un poco de crecimiento):

// top-level handler 
Action tlh(Action act) { 
    return() => { 
     while(true) { 
      try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); } 
     } 
    } 
} 

gh() { 
    try { g(); } catch(Bounce e) { 
     throw new Bounce(tlh(() => { 
      e.run(); 
      try { h(); } catch(StackOverflowError e) { 
       throw new Bounce(tlh(() => h()); 
      } 
     }); 
    } 
    try { h(); } catch(StackOverflowError e) { 
     throw new Bounce(tlh(() => h())); 
    } 
} 

El principal beneficio aquí es si no se lanza ninguna excepción, este es el mismo código que comenzamos con solo algunos extractores de excepciones instalados. Como las llamadas finales (la llamada h()) no manejan la excepción Bounce, esa excepción volará por ellas desenrollando esos fotogramas (innecesarios) de la pila. Las llamadas sin seguimiento detectan las excepciones de Bounce y las vuelven a lanzar con el código restante agregado. Esto desenrollará la pila hasta el nivel superior, eliminando los marcos de llamada de cola pero recordando los marcos de llamadas sin cola en el procedimiento nullary. Cuando finalmente ejecutemos el procedimiento en la excepción de Bounce en el nivel superior, recrearemos todos los marcos de llamadas que no sean de cola. En este punto, si inmediatamente nos quedamos sin pila otra vez, entonces, dado que no reinstalamos los manejadores StackOverflowError, no se detectará como se desee, ya que realmente estamos fuera de pila. Si avanzamos un poco más, se instalará un nuevo StackOverflowError según corresponda. Además, si progresamos, pero luego nos quedamos sin pila otra vez, no hay beneficio en volver a desenrollar los cuadros que ya hemos desenrollado, por lo que instalamos nuevos manejadores de nivel superior para que la pila solo se desenrolle.

El mayor problema con este enfoque es que probablemente desee llamar a los métodos Java normales y que tenga un espacio de pila arbitrariamente pequeño cuando lo haga, para que tengan suficiente espacio para comenzar pero no terminar y no puede retomarlos en el medio. Hay al menos dos soluciones para esto. El primero es enviar todo ese trabajo a un hilo separado que tendrá su propia pila. Esto es bastante efectivo y bastante fácil, y no introducirá ninguna concurrencia (a menos que lo desee). Otra opción es simplemente desenrollar deliberadamente la pila antes de llamar a cualquier método Java normal simplemente lanzando un StackOverflowError inmediatamente antes que ellos. Si aún se queda sin espacio en la pila cuando reanudas, entonces fuiste jodido para empezar.

Una cosa similar se puede hacer para hacer continuaciones justo a tiempo también. Desafortunadamente, esta transformación no es realmente soportable hacerla a mano en Java, y es probablemente límite para lenguajes como C# o Scala. Entonces, las transformaciones como esta tienden a ser hechas por lenguajes que se dirigen a la JVM y no por personas.

-1
/* 
Using Throwable we can trap any know error in JAVA.. 
*/ 
public class TestRecur { 
    private int i = 0; 


    public static void main(String[] args) { 
     try { 
      new TestRecur().show(); 
     } catch (Throwable err) { 
      System.err.println("Error..."); 
     } 
    } 

    private void show() { 
     System.out.println("I = " + i++); 
     show(); 
    } 
} 

// Sin embargo u puede tener una mirada en el enlace: http://marxsoftware.blogspot.in/2009/07/diagnosing-and-resolving.html a // entender los snipets código de error que puede elevar

1

La mayoría de las posibilidades de conseguir StackOverflowError son infinitas utilizando [largas]/recurrencias en funciones recursivas

Puede evitar la recursión de funciones cambiando el diseño de la aplicación para usar objetos de datos apilables. Existen patrones de codificación para convertir códigos recursivos en bloques de código iterativo. Echar un vistazo a continuación answeres:

Así, se evita la memoria de apilamiento de Java para sus llamadas de función recesivos, mediante el uso de sus propias pilas de datos.

0

en alguna ocasión, no puede atrapar stackoverflowerror. cada vez que lo intentes, encontrarás uno nuevo. porque es java vm. es bueno encontrar bloques de código recursivos, como Andrew Bullock's said.

Cuestiones relacionadas