usar error como java stack-overflow

java - como - Cómo obtener la pila completa de StackOverflowError



como usar stack overflow (5)

Activaría un volcado de hilo manual mientras reproduzco el problema. Lo más probable es que el stackoverflow sea lanzado solo después de un tiempo. Por lo tanto, podemos desencadenar rápidamente un volcado de subprocesos en jvm que nos dará detalles sobre la persona que llama imprimiendo toda la pila de la hebra problemática antes de que su pila se salte.

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

Considera este simple 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 main y a método en el seguimiento de pila. Mi conjetura es que esto se debe a un desbordamiento, la entrada más nueva en la pila reemplaza la más antigua (?).

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

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


Intentaría conectar algo para decorar la salida de seguimiento de pila similar a ExceptionUtils para agrupar llamadas repetidas a la misma Clase o Paquete.


La JVM tiene un límite artificial de 1024 entradas que puede tener en el seguimiento de la pila de una Excepción o Error, probablemente para ahorrar memoria cuando ocurra (ya que la VM tiene que asignar memoria para almacenar el seguimiento de la pila).

Afortunadamente, hay una bandera que permite aumentar este límite. Simplemente ejecute su programa con el siguiente argumento:

-XX:MaxJavaStackTraceDepth=1000000

Esto imprimirá hasta 1 millón de entradas de su seguimiento de 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 ilimitado.

Esta lista de opciones de JVM no estándar proporciona más detalles:

Max. no. de líneas en el seguimiento de la pila para excepciones de Java (0 significa todas). Con Java> 1.6, el valor 0 realmente significa 0. valor -1 o cualquier número negativo debe especificarse 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 se ahoga en un número negativo (probado con 1.5.0_22 en Windows).

Ejecutar la muestra de la pregunta con esta bandera da el siguiente resultado:

Exception in thread "main" java.lang.Error 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, puede encontrar los llamadores originales del código que arrojó el Error, incluso si el seguimiento real de la pila tiene más de 1024 líneas.

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

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

Esencialmente, esto creará y lanzará un nuevo Error , descartando la última entrada porque cada uno se enviará un nivel superior en comparación con el anterior (esto puede tardar unos segundos, porque todos estos errores deben crearse). Cuando la traza de pila se reducirá a 1023 elementos, simplemente se vuelve a derribar.

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


Por lo que sé, no es posible obtener el seguimiento completo de la pila (sin embargo, no sé realmente por qué).

Sin embargo, lo que podría hacer para rastrear el problema es verificar manualmente la profundidad de la pila en su código afectado de la siguiente 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 necesario encontrar SOME_VALUE mediante experimentación (lo suficientemente alto como para no ser activado 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: parece que me he perdido el hecho de que el problema se produce en Pattern , lo que complica las cosas. Sin embargo, podría usar un punto de interrupción del método condicional en uno de los métodos de Pattern en el seguimiento de pila con una condición como esta (el valor real podría necesitar ajustes):

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

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


Si se está quedando sin pila, considere crear un subproceso dedicado con suficiente pila especialmente para ejecutar la solicitud. Código de muestra 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); } } }