2011-03-02 15 views
23

¿Al observar StackOverflowError cómo recuperar la pila de llamadas completa?Cómo obtener la pila completa de StackOverflowError

consideran este sencillo ejemplo:

public class Overflow { 

    public Overflow() { 
     new Overflow(); 
    } 
    public static void a() { 
     new Overflow(); 
    } 
    public static void main(String[] argv) { 
     a(); 
    } 
} 

Ahora el error reportado es:

Exception in thread "main" java.lang.StackOverflowError 
    at Overflow.<init>(Overflow.java:11) 
    [last line repeated many times] 

pero no puedo ver el método main y a en el seguimiento de la pila. Supongo que esto se debe al desbordamiento, la entrada más nueva en la pila reemplaza a la más antigua (?).

Ahora, ¿cómo obtener las entradas de la pila a y main en la salida?

El fondo es el StackOverflowError (pero eso no es una recursión infinita, porque no ocurre cuando se aumenta el tamaño de la pila) y es difícil detectar el problema en el código. Solo obtengo las líneas múltiples de java.util.regex.Pattern pero no la información que el código llamó eso. La aplicación es demasiado complicada para establecer un punto de interrupción en cada llamada a Pattern s.

+0

Dado que puede aumentar el tamaño de la pila y desaparece, ¿puede intentar disminuir el tamaño de la pila y ver si eso le permite ver más trazas de la pila? No recuerdo cómo se controla el tamaño de la pila en la JVM y si se le permite hacerlo lo suficientemente pequeño, pero podría ayudar a diagnosticar el problema. –

+0

@Tom, el tamaño de la pila está controlado por el nuevo 'Tema (grupo ThreadGroup, destino Runnable, nombre de cadena, stackSize largo)'. – bestsss

+0

Oh bien. Buena idea :-( –

Respuesta

27

La JVM tiene un límite artificial de 1024 entradas que puede tener en el seguimiento de la pila de una excepción o un error, probablemente para ahorrar memoria cuando ocurre (ya que la máquina virtual tiene que asignar memoria para almacenar el seguimiento de la pila)

Afortunadamente, hay una bandera que permite aumentar este límite.Sólo tiene que ejecutar su programa con el siguiente argumento:

-XX:MaxJavaStackTraceDepth=1000000 

Esto imprimirá hasta 1 millón de entradas de su seguimiento de la pila, lo que debería ser más que suficiente. También es posible establecer este valor en -1 para establecer el número de entradas como ilimitadas.

This list of non-standard JVM options da más detalles:

Max. no. de líneas en el seguimiento de la pila para excepciones de Java (0 significa todo). Con Java> 1.6, el valor 0 realmente significa 0. valor -1 o cualquier número negativo debe ser especificado para imprimir toda la pila (probado con 1.6.0_22, 1.7.0 en Windows). Con Java < = 1.5, el valor 0 significa todo, JVM ahoga el número negativo (probado con 1.5.0_22 en Windows).

Ejecución de la muestra de la pregunta con esta bandera da el siguiente resultado:

Exception in thread "main" java.lang.StackOverflowError 
    at Overflow.<init>(Overflow.java:3) 
    at Overflow.<init>(Overflow.java:4) 
    at Overflow.<init>(Overflow.java:4) 
    at Overflow.<init>(Overflow.java:4) 
(more than ten thousand lines later:) 
    at Overflow.<init>(Overflow.java:4) 
    at Overflow.<init>(Overflow.java:4) 
    at Overflow.a(Overflow.java:7) 
    at Overflow.main(Overflow.java:10) 

De esta manera, se pueden encontrar las personas que llaman originales del código que arrojó el error, incluso si el seguimiento de pila real tiene más de 1024 líneas de largo.

Si no puede usar esa opción, existe otra manera, si está en una función recursiva como esta, y si puede modificarla. Si se agrega el siguiente try-catch:

public Overflow() { 
    try { 
     new Overflow(); 
    } 
    catch(StackOverflowError e) { 
     StackTraceElement[] stackTrace = e.getStackTrace(); 
     // if the stack trace length is at the limit , throw a new StackOverflowError, which will have one entry less in it. 
     if (stackTrace.length == 1024) { 
      throw new StackOverflowError(); 
     } 
     throw e; // if it is small enough, just rethrow it. 
    } 
} 

Esencialmente, esto va a crear y lanzar una nueva StackOverflowError, descartando la última entrada, ya que cada uno se enviarán en un nivel superior en comparación con la anterior (esto puede tomar una unos segundos, porque todos estos errores deben ser creados). Cuando la traza de la pila se reducirá a 1023 elementos, simplemente se volverá a tirar.

En última instancia, esto imprimirá las 1023 líneas en la parte inferior de la traza de la pila, que no es la traza completa de la pila, pero es probablemente la parte más útil de la pila.

4

Hasta donde yo sé, no es posible obtener el seguimiento completo de la pila (sin embargo, realmente no sé por qué).

Sin embargo, lo que podría hacer para localizar el problema, es comprobar manualmente para la profundidad de la pila en su código afectado de esta manera:

StackTraceElement[] trace = Thread.currentThread().getStackTrace(); 
if (trace.length > SOME_VALUE) { 
    // trigger some diagnostic action, print a stack trace or have a breakpoint here 
} 

SOME_VALUE tendría que ser encontrado por experimentación (lo suficiente como para alta no se activará en situaciones "buenas" y lo suficientemente bajo como para no ser inalcanzable). Por supuesto, esto ralentizaría su código y solo debería usarse para depurar el problema.

Actualización: que parecen haber perdido que el problema se produce en Pattern, lo que complica las cosas. Sin embargo, se puede utilizar un método de punto de interrupción condicional en uno de los Pattern métodos en el seguimiento de la pila con una condición como esta (el valor real podría necesitar ajustes):

Thread.currentThread().getStackTrace().length > 300 

De esta manera usted puede encontrar su propio código en el parte inferior de la pila cuando llegas al punto de interrupción.

+0

* Por lo que yo sé, no es posible obtener el seguimiento completo de la pila (sin embargo, realmente no sé por qué). * Una razón obvia para que se quede sin memoria e incluso intente recopilarla y reconstruirla. Stack Traces no es requerido por Java mismo (seguro por razones de seguridad). 300 es generalmente de baja profundidad (a menos que haya toneladas de variables en la pila) pero la recopilación de la pila no es así. – bestsss

+0

gracias. ¡El punto de interrupción condicional hizo el trabajo! –

0

Intentaré enchufar algo para decorar la salida de seguimiento de la pila similar a ExceptionUtils para agrupar llamadas repetidas a la misma clase o paquete.

+0

Creo que el punto es que el seguimiento de pila reportado por java se detiene antes de la parte inferior (es decir, principal), por lo que la información no está allí para informar. Presumiblemente se trata de una capacidad máxima de los elementos que informa, y ​​cuando ocurre el desbordamiento de la pila, el programa ha ido más allá de ese límite. Lo que es un poco extraño es que esta capacidad no es la misma que la profundidad a la que se produce el desbordamiento de la pila. –

1

Si se está quedando sin pila, considere la posibilidad de crear un subproceso dedicado con suficiente pila, especialmente para ejecutar la solicitud. Muestra de código a continuación.

package t1; 

import java.util.concurrent.Callable; 
import java.util.concurrent.CancellationException; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.RejectedExecutionHandler; 
import java.util.concurrent.SynchronousQueue; 
import java.util.concurrent.ThreadFactory; 
import java.util.concurrent.ThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 
import java.util.concurrent.atomic.AtomicLong; 
import java.util.regex.Pattern; 

public class RegExpRunner { 
    ExecutorService svc;  
    public RegExpRunner(long stackSize){ 
     init(stackSize); 

    } 


    void init(long stackSize){ 
     final SynchronousQueue<Runnable> queue = new SynchronousQueue<Runnable>(); 

     svc = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS, queue, createThreadFactory(stackSize), new RejectedExecutionHandler(){//wait if there is a concurrent compile and no available threads 
      @Override 
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 
       try{ 
        queue.put(r); 
       }catch(InterruptedException _ie){ 
        Thread.currentThread().interrupt(); 
        throw new IllegalStateException(_ie); 
       } 
      }     
     }); 
    } 

    private ThreadFactory createThreadFactory(final long stackSize) {  
     return new ThreadFactory(){ 
      final ThreadGroup g = Thread.currentThread().getThreadGroup(); 
      private final AtomicLong counter= new AtomicLong(); 
      { 
       //take care of contextClassLoader and AccessControlContext    
      } 

      @Override 
      public Thread newThread(Runnable r) {    
       Thread t = new Thread(g, r, composeName(r), stackSize); 
       return t; 
      } 

      protected String composeName(Runnable r) { 
       return String.format("Regexp dedicated compiler: %d @ %tF %<tT ", counter.incrementAndGet(), System.currentTimeMillis()); 
      } 
     }; 
    }; 

    public Pattern compile(final String regex){//add flags if you need 'em 
     Callable<Pattern> c = new Callable<Pattern>(){ 
      @Override 
      public Pattern call() throws Exception { 
       return Pattern.compile(regex); 
      }   
     }; 

     try{ 
      Pattern p = svc.submit(c).get(); 
      return p; 
     }catch(InterruptedException _ie){ 
      Thread.currentThread().interrupt(); 
      throw new IllegalStateException(_ie); 
     } catch(CancellationException _cancel){ 
      throw new AssertionError(_cancel);//shan't happen 
     } catch(ExecutionException _exec){ 
      Throwable t = _exec.getCause(); 
      if (t instanceof RuntimeException) throw (RuntimeException) t; 
      if (t instanceof Error) throw (Error) t; 
      throw new IllegalStateException(t==null?_exec:t); 
     } 


    } 
} 
0

Me gustaría activar un volcado manual de hilo mientras reproduzco el problema. Lo más probable es que el stackoverflow se arroje solo después de un tiempo. Por lo tanto, podemos activar rápidamente un volcado de subprocesos en jvm que nos dará detalles acerca de la persona que llama mediante la impresión de toda la pila del subproceso problemático antes de que su pila se sobrevuele.

Cuestiones relacionadas