Comment obtenir la pleine pile de StackOverflowError

lors de l'observation D'une erreur de stackoverflow, comment récupérer la pile d'appels complète?

Considérer cet exemple simple:

public class Overflow {

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

Maintenant, l'erreur signalée est:

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

Mais je ne vois pas l' main et a méthode dans la trace de la pile. À mon avis, c'est à cause du débordement, la plus récente entrée sur la pile remplace la plus ancienne (?).

maintenant, comment obtenir le a et main pile entrées de la sortie?

l'arrière-plan est que j'obtiens l'a Stacoverflowerror (mais ce n'est pas une récursion infinie, parce que ça n'arrive pas quand on augmente la taille de la pile) et il est difficile de repérer le problème dans le code. Je ne reçois que les multiples lignes de java.util.regex.Pattern mais pas l'information que le code appelait ça. L'application est trop compliquée pour définir un point de rupture sur chaque appel à Pattern S.

24
demandé sur skaffman 2011-03-02 12:50:09

5 réponses

la JVM a une limite artificielle de 1024 entrées que vous pouvez avoir dans la trace de pile d'une Exception ou D'une erreur, probablement pour sauver de la mémoire quand elle se produit (puisque la VM doit allouer de la mémoire pour stocker la trace de pile).

Heureusement, il existe un indicateur qui permet d'augmenter cette limite. Lancez simplement votre programme avec l'argument suivant:

-XX:MaxJavaStackTraceDepth=1000000

cela imprimera jusqu'à 1 million d'entrées de votre trace de pile, ce qui devrait être plus que suffisant. Il est également possible pour définir cette valeur à -1 pour définir le nombre d'entrées illimité.

Cette liste non-standard des options JVM donne plus de détails:

Max. aucun. de lignes dans la trace de la pile pour Java exceptions (0 tout.) Avec Java > 1.6, la valeur 0 signifie vraiment 0. la valeur -1 ou tout le nombre négatif doit être spécifié pour imprimer toute la pile (testé avec 1.6.0_22, 1.7.0 sous Windows). Avec Java < = 1.5, la valeur 0 signifie tout, JVM s'étouffe sur négatif nombre (testé avec 1.5.0_22 sur Windows.)

L'exécution de l'échantillon de la question avec ce drapeau donne le résultat suivant:

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 cette façon, vous pouvez trouver les appelants originaux du code qui a lancé l'erreur, même si la trace réelle de la pile est longue de plus de 1024 lignes.

Si vous ne pouvez pas utiliser cette option, il ya encore une autre façon, si vous êtes dans une fonction récursive comme cela, et si vous pouvez le modifier. Si vous ajoutez ce qui suit 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.
    }
}

essentiellement, cela créera et lancera un nouveau StackOverflowError, en rejetant la dernière entrée parce que chacun sera envoyé d'un niveau par rapport à la précédente (cela peut prendre quelques secondes, parce que toutes ces Erreurs doivent être créés). Lorsque la trace de la pile sera réduite à 1023 éléments, elle est tout simplement repensée.

en fin de compte, cela affichera les 1023 lignes au bas de la trace de la pile, qui n'est pas la trace complète de la pile, mais qui est probablement la partie la plus utile.

31
répondu Cyrille Ka 2013-10-12 05:59:35

autant que je sache, il n'est pas possible d'obtenir la trace complète de la pile (cependant, je ne sais pas vraiment pourquoi).

cependant, ce que vous pouvez faire pour retrouver le problème, c'est de vérifier manuellement la profondeur de la pile dans votre code affecté comme ceci:

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 devrait être trouvé par expérimentation (assez haut pour ne pas être déclenché dans les "bonnes" situations et assez bas pour ne pas être inaccessible). Bien sûr, cela ralentirait votre code et ne devrait être utilisé que pour déboguer le problème.

mise à Jour: il me semble avoir raté le problème se produit dans Pattern, ce qui complique les choses. Cependant, vous pouvez utiliser un point de rupture de méthode conditionnelle à l'un des Pattern méthodes dans la trace de la pile avec une condition comme ceci (la valeur réelle pourriez avoir besoin de réglages):

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

de Cette façon, vous pouvez trouver votre propre code au bas de la pile quand vous frappez le point d'arrêt.

4
répondu Joachim Sauer 2011-03-02 10:02:01

si vous êtes à court de stack, pensez à créer un thread dédié avec suffisamment de stack pour lancer la requête. Exemple de code ci-dessous.

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


    }
}
1
répondu bestsss 2011-03-02 11:00:20

j'essaierais de brancher quelque chose pour décorer la sortie de trace stack similaire à ExceptionUtils grouper les appels répétés à la même classe ou au même paquet.

0
répondu David O'Meara 2011-03-02 10:04:13

Je déclencherais un dump manuel de thread pendant que je reproduis le problème. Probablement le stackoverflow est levée qu'après un certain temps. Ainsi, nous pouvons rapidement déclencher un dump de Thread sur jvm qui nous donnera des détails sur l'appelant en imprimant la pile entière du thread problématique avant que sa pile soit survolée.

0
répondu anony 2016-06-08 13:04:55