Question Qu'est-ce qu'une trace de pile, et comment puis-je l'utiliser pour déboguer mes erreurs d'application?


Parfois, lorsque je lance mon application, il me donne une erreur qui ressemble à:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Les gens ont qualifié cela de «trace de pile». Qu'est-ce qu'une trace de pile? Que peut-il me dire au sujet de l'erreur qui se passe dans mon programme?


A propos de cette question - assez souvent je vois une question qui se pose lorsqu'un programmeur débutant "reçoit une erreur", et il colle simplement sa trace de pile et un bloc de code aléatoire sans comprendre ce qu'est la trace de la pile ou comment l'utiliser. Cette question est destinée à servir de référence pour les programmeurs novices qui pourraient avoir besoin d'aide pour comprendre la valeur d'une trace de pile.


518
2017-10-21 14:52


origine


Réponses:


En termes simples, un trace de la pile est une liste des appels de méthode que l'application était au milieu de quand une exception a été levée.

Exemple simple

Avec l'exemple donné dans la question, nous pouvons déterminer exactement où l'exception a été levée dans l'application. Jetons un coup d'oeil à la trace de la pile:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

C'est une trace de pile très simple. Si nous commençons au début de la liste de "à ...", nous pouvons dire où notre erreur est survenue. Ce que nous cherchons est le le plus haut appel de méthode qui fait partie de notre application. Dans ce cas, c'est:

at com.example.myproject.Book.getTitle(Book.java:16)

Pour déboguer cela, nous pouvons ouvrir Book.java et regardez la ligne 16, lequel est:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

Cela indiquerait que quelque chose (probablement title) est null dans le code ci-dessus.

Exemple avec une chaîne d'exceptions

Parfois, les applications interceptent une exception et la rejettent comme cause d'une autre exception. Cela ressemble généralement à:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

Cela peut vous donner une trace de pile qui ressemble à:

Exception in thread "main" java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.java:22)
        at com.example.myproject.Author.getBookIds(Author.java:36)
        ... 1 more

Ce qui est différent à propos de celui-ci est le "Causé par". Parfois, les exceptions auront plusieurs sections "Causé par". Pour ceux-ci, vous voulez généralement trouver la "cause première", qui sera l'une des plus faibles "Causée par" dans la trace de la pile. Dans notre cas, c'est:

Caused by: java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.java:22) <-- important line

Encore une fois, avec cette exception, nous voudrions regarder la ligne 22 de Book.java pour voir ce qui pourrait causer le NullPointerException ici.

Exemple plus intimidant avec le code de la bibliothèque

Habituellement, les traces de pile sont beaucoup plus complexes que les deux exemples ci-dessus. Voici un exemple (c'est long, mais montre plusieurs niveaux d'exceptions chaînées):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
    ... 32 more
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
    ... 54 more

Dans cet exemple, il y en a beaucoup plus. Ce qui nous préoccupe le plus, c'est de chercher des méthodes qui viennent de notre code, ce qui serait quelque chose dans le com.example.myproject paquet. Dans le deuxième exemple (ci-dessus), nous voulons d'abord rechercher la cause première, qui est:

Caused by: java.sql.SQLException

Cependant, tous les appels de méthode en dessous sont du code de bibliothèque. Donc, nous allons passer au "Causé par" ci-dessus, et chercher le premier appel de méthode provenant de notre code, qui est:

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

Comme dans les exemples précédents, nous devrions regarder MyEntityService.java en ligne 59, parce que c'est là que cette erreur est apparue (celle-ci est un peu évidente ce qui s'est mal passé, puisque la SQLException indique l'erreur, mais la procédure de débogage est ce que nous recherchons).


479
2017-10-21 14:52



Je poste cette réponse de sorte que la réponse la plus haute (quand triée par activité) n'est pas une qui est tout simplement faux.

Qu'est-ce qu'un Stacktrace?

Un stacktrace est un outil de débogage très utile. Il montre la pile d'appels (c'est-à-dire la pile de fonctions qui a été appelée jusqu'à ce moment) au moment où une exception non interceptée a été lancée (ou l'heure à laquelle la pile a été générée manuellement). Ceci est très utile car il ne montre pas seulement où l'erreur s'est produite, mais aussi comment le programme s'est retrouvé à cet endroit du code. Cela conduit à la question suivante:

Qu'est-ce qu'une exception?

Une exception est ce que l'environnement d'exécution utilise pour vous signaler qu'une erreur s'est produite. Les exemples les plus courants sont NullPointerException, IndexOutOfBoundsException ou ArithmeticException. Chacun d'entre eux sont causés lorsque vous essayez de faire quelque chose qui n'est pas possible. Par exemple, une exception NullPointerException sera levée lorsque vous essayez de déréférencer un objet Null:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

Comment dois-je faire face à Stacktraces / Exceptions?

Au début, découvrez ce qui cause l'exception. Essayez googleing le nom de l'exception pour savoir, quelle est la cause de cette exception. La plupart du temps, cela sera causé par un code incorrect. Dans les exemples ci-dessus, toutes les exceptions sont causées par un code incorrect. Donc, pour l'exemple NullPointerException, vous pouvez vous assurer que a n'est jamais nul à ce moment-là. Vous pourriez, par exemple, initialiser a ou inclure un chèque comme celui-ci:

if (a!=null) {
    a.toString();
}

De cette façon, la ligne incriminée n'est pas exécutée si a==null. Idem pour les autres exemples.

Parfois, vous ne pouvez pas vous assurer que vous n'obtenez pas d'exception. Par exemple, si vous utilisez une connexion réseau dans votre programme, vous ne pouvez pas empêcher l'ordinateur de perdre sa connexion Internet (par exemple, vous ne pouvez pas empêcher l'utilisateur de déconnecter la connexion réseau de l'ordinateur). Dans ce cas, la bibliothèque réseau lèvera probablement une exception. Maintenant, vous devriez attraper l'exception et manipuler il. Cela signifie que, dans l'exemple avec la connexion réseau, vous devriez essayer de rouvrir la connexion ou informer l'utilisateur ou quelque chose comme ça. De plus, à chaque fois que vous utilisez catch, attrapez toujours uniquement l'exception que vous voulez attraper, n'utilisez pas de déclarations de catch larges comme catch (Exception e) cela attraperait toutes les exceptions. Ceci est très important, car sinon vous pourriez accidentellement attraper la mauvaise exception et réagir de la mauvaise façon.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

Pourquoi devrais-je pas utiliser catch (Exception e)?

Utilisons un petit exemple pour montrer pourquoi vous ne devriez pas simplement saisir toutes les exceptions:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

Ce que ce code essaie de faire est d'attraper le ArithmeticException causé par une division possible par 0. Mais il attrape aussi un possible NullPointerException qui est jeté si a ou b sont null. Cela signifie que vous pourriez obtenir un NullPointerException mais vous allez le traiter comme une exception ArithmeticException et probablement faire la mauvaise chose. Dans le meilleur des cas, il vous manque encore une NullPointerException. Des trucs comme ça rendent le débogage beaucoup plus difficile, alors ne faites pas ça.

TLDR

  1. Déterminez quelle est la cause de l'exception et corrigez-la, afin qu'elle ne lève pas l'exception du tout.
  2. Si 1. n'est pas possible, attraper l'exception spécifique et la gérer.

    • N'ajoutez jamais un try / catch et ignorez simplement l'exception! Ne fais pas ça!
    • Ne jamais utiliser catch (Exception e), attrapez toujours des exceptions spécifiques. Cela vous permettra d'économiser beaucoup de maux de tête.

65
2017-10-14 10:40



Pour ajouter à ce que Rob a mentionné. La définition de points de rupture dans votre application permet le traitement pas-à-pas de la pile. Cela permet au développeur d'utiliser le débogueur pour voir à quel moment la méthode est en train de faire quelque chose qui n'était pas prévu.

Depuis que Rob a utilisé le NullPointerException (NPE) pour illustrer quelque chose de commun, nous pouvons vous aider à supprimer ce problème de la manière suivante:

si nous avons une méthode qui prend des paramètres tels que: void (String firstName) 

Dans notre code, nous voulons évaluer cela firstName contient une valeur, nous ferions ceci comme ceci: if(firstName == null || firstName.equals("")) return;

Ce qui précède nous empêche d'utiliser firstName en tant que paramètre dangereux. Par conséquent, en faisant des vérifications nuls avant le traitement, nous pouvons aider à assurer que notre code fonctionnera correctement. Pour développer un exemple qui utilise un objet avec des méthodes, nous pouvons regarder ici:

if(dog == null || dog.firstName == null) return;

Ce qui précède est le bon ordre pour vérifier les valeurs NULL, nous commençons par l'objet de base, chien dans ce cas, puis commençons à parcourir l'arborescence des possibilités pour nous assurer que tout est valide avant le traitement. Si l'ordre était inversé, un NPE pourrait potentiellement être lancé et notre programme tomberait en panne.


17
2017-10-21 15:05



Il existe une autre fonctionnalité de stacktrace offerte par la famille Throwable - la possibilité de manipuler pile informations de trace.

Comportement standard:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Trace de la pile:

Exception in thread "main" java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.java:9)
    at test.stack.trace.SomeClass.main(SomeClass.java:27)

Trace de pile manipulée:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Trace de la pile:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)

14
2017-09-16 17:34



Pour comprendre le nom: Une trace de pile est une liste d'exceptions (ou vous pouvez dire une liste de "Cause par"), de l'exception la plus superficielle (par exemple Exception de couche de service) à la plus profonde (Exception de base de données). Tout comme la raison pour laquelle nous l'appelons 'stack' est parce que la pile est First in Last out (FILO), l'exception la plus profonde s'est produite au tout début, puis une chaîne d'exception a été générée, l'Exception de surface était la dernière on est arrivé à temps, mais on le voit en premier lieu.

Clé 1: Une chose difficile et importante à comprendre ici est la suivante: la cause la plus profonde n'est peut-être pas la "cause première", car si vous écrivez un "mauvais code", cela peut provoquer une exception plus profonde que sa couche. Par exemple, une requête SQL incorrecte peut provoquer la réinitialisation de la connexion SQLServerException dans le bottem au lieu de l'erreur syndax, qui peut se trouver juste au milieu de la pile.

-> Localiser la cause racine au milieu est votre travail. enter image description here

Clé 2: Une autre chose délicate mais importante est à l'intérieur de chaque bloc "Cause par", la première ligne était la couche la plus profonde et arrive en première place pour ce bloc. Par exemple,

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
           at com.example.myproject.Author.getBookTitles(Author.java:25)
               at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Book.java:16 a été appelé par Auther.java:25 qui était appelé par Bootstrap.java:14, Book.java:16 était la cause principale. Ci-joint un diagramme trier la pile de traces dans l'ordre chronologique. enter image description here


10
2017-07-26 02:24



Juste pour ajouter aux autres exemples, il y a classes internes (imbriquées) qui apparaissent avec le $ signe. Par exemple:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

Cela entraînera cette trace de pile:

Exception in thread "main" java.lang.RuntimeException
        at Test.privateMethod(Test.java:4)
        at Test.access$000(Test.java:1)
        at Test$1.run(Test.java:10)
        at Test.main(Test.java:13)

8
2018-04-19 03:43



Les autres messages décrivent ce qu'est une trace de pile, mais il peut être difficile de travailler avec.

Si vous obtenez une trace de pile et que vous voulez retracer la cause de l’exception, un bon point de départ pour comprendre Console de trace Java Stack dans Éclipse. Si vous utilisez un autre IDE, il peut y avoir une fonctionnalité similaire, mais cette réponse concerne Eclipse.

Tout d'abord, assurez-vous que toutes vos sources Java sont accessibles dans un projet Eclipse.

Puis dans le Java perspective, cliquez sur le Console onglet (généralement en bas). Si la vue Console n'est pas visible, accédez à l'option de menu Fenêtre -> Afficher la vue et sélectionnez Console.

Ensuite, dans la fenêtre de la console, cliquez sur le bouton suivant (sur la droite)

Consoles button

puis sélectionnez Console de trace Java Stack dans la liste déroulante.

Collez votre trace de pile dans la console. Il fournira ensuite une liste de liens dans votre code source et tout autre code source disponible.

C'est ce que vous pourriez voir (image de la documentation Eclipse):

Diagram from Eclipse documentation

L'appel de méthode le plus récent sera le Haut de la pile, qui est la ligne supérieure (à l'exclusion du texte du message). Descendre la pile remonte dans le temps. La deuxième ligne est la méthode qui appelle la première ligne, etc.

Si vous utilisez un logiciel open source, vous devrez peut-être télécharger et joindre à votre projet les sources si vous souhaitez les examiner. Téléchargez les fichiers source, dans votre projet, ouvrez le Bibliothèques référencées dossier pour trouver votre pot pour votre module open-source (celui avec les fichiers de classe) puis faites un clic droit, sélectionnez Propriétés et attachez le pot source.


5
2018-03-12 09:34