Pregunta ¿Qué es un seguimiento de pila y cómo puedo usarlo para depurar mis errores de aplicación?


A veces, cuando ejecuto mi aplicación me da un error que se ve así:

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)

La gente se ha referido a esto como un "rastro de pila". ¿Qué es un rastro de pila? ¿Qué puede decirme sobre el error que está sucediendo en mi programa?


Sobre esta pregunta, con frecuencia veo surgir la pregunta de que un programador novato está "recibiendo un error" y simplemente pegan su rastro de pila y algún bloque aleatorio de código sin entender qué es el seguimiento de la pila o cómo pueden usarlo. Esta pregunta pretende ser una referencia para los programadores novatos que podrían necesitar ayuda para comprender el valor de un seguimiento de pila.


518
2017-10-21 14:52


origen


Respuestas:


En términos simples, un rastro de pila es una lista de las llamadas a métodos que la aplicación estaba en el medio de cuando se lanzó una excepción.

Ejemplo simple

Con el ejemplo dado en la pregunta, podemos determinar exactamente dónde se lanzó la excepción en la aplicación. Echemos un vistazo al rastro de la pila:

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)

Este es un rastro de pila muy simple. Si comenzamos al principio de la lista de "en ...", podemos decir dónde ocurrió nuestro error. Lo que estamos buscando es el más alto llamada a método que es parte de nuestra aplicación. En este caso, es:

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

Para depurar esto, podemos abrir Book.java y mira la línea 16, cual es:

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

Esto indicaría que algo (probablemente title) es null en el código anterior.

Ejemplo con una cadena de excepciones

A veces, las aplicaciones capturan una excepción y la vuelven a lanzar como causa de otra excepción. Esto generalmente se ve así:

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   }

Esto podría darte un rastro de pila que se ve así:

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

Lo que es diferente acerca de este es el "Causado por". A veces, las excepciones tendrán múltiples secciones "Causadas por". Para estos, normalmente desea encontrar la "causa raíz", que será una de las secciones "Causadas por" más bajas en el seguimiento de la pila. En nuestro caso, es:

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

Nuevamente, con esta excepción, querríamos mirar la línea 22 de Book.java para ver lo que podría causar el NullPointerException aquí.

Más desalentador ejemplo con código de biblioteca

Por lo general, los rastros de pila son mucho más complejos que los dos ejemplos anteriores. Aquí hay un ejemplo (es largo, pero muestra varios niveles de excepciones encadenadas):

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

En este ejemplo, hay mucho más. Lo que más nos preocupa es buscar métodos que sean de nuestro código, que sería cualquier cosa en el com.example.myproject paquete. En el segundo ejemplo (arriba), lo primero que debemos hacer es buscar la causa raíz, que es:

Caused by: java.sql.SQLException

Sin embargo, todas las llamadas de método debajo de eso son código de biblioteca. Así que nos moveremos hasta el "Caused by" que está arriba y buscaremos la primera llamada a método que se origina en nuestro código, que es:

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

Como en ejemplos anteriores, deberíamos mirar MyEntityService.java en línea 59, porque ahí es donde se originó este error (este es un poco obvio lo que salió mal, ya que la SQLException indica el error, pero el procedimiento de depuración es lo que buscamos).


479
2017-10-21 14:52



Estoy publicando esta respuesta por lo que la respuesta más alta (cuando se ordena por actividad) no es simplemente incorrecta.

¿Qué es una StackTrace?

Una stacktrace es una herramienta de depuración muy útil. Muestra la pila de llamadas (es decir, la pila de funciones que se llamaron hasta ese punto) en el momento en que se lanzó una excepción no detectada (o el momento en que la pila se generó manualmente). Esto es muy útil porque no solo muestra dónde ocurrió el error, sino también cómo el programa terminó en ese lugar del código. Esto lleva a la siguiente pregunta:

¿Qué es una excepción?

Una excepción es lo que utiliza el entorno de ejecución para indicarle que se produjo un error. Los ejemplos populares son NullPointerException, IndexOutOfBoundsException o ArithmeticException. Cada uno de estos se producen cuando intentas hacer algo que no es posible. Por ejemplo, se lanzará una NullPointerException cuando intente desreferenciar un objeto nulo:

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.

¿Cómo debo lidiar con Stacktraces / Exceptions?

Al principio, averigua qué está causando la excepción. Intenta buscar en google el nombre de la excepción para averiguar cuál es la causa de esa excepción. La mayoría de las veces será causada por un código incorrecto. En los ejemplos dados anteriormente, todas las excepciones son causadas por código incorrecto. Entonces, para el ejemplo de NullPointerException, puedes asegurarte de que a nunca es nulo en ese momento. Podría, por ejemplo, inicializar a o incluya un cheque como este:

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

De esta manera, la línea ofensiva no se ejecuta si a==null. Lo mismo ocurre con los otros ejemplos.

A veces no puedes asegurarte de que no obtienes una excepción. Por ejemplo, si está utilizando una conexión de red en su programa, no puede evitar que la computadora pierda su conexión a Internet (por ejemplo, no puede evitar que el usuario desconecte la conexión de red de la computadora). En este caso, la biblioteca de la red probablemente lanzará una excepción. Ahora deberías ver la excepción y encargarse de eso. Esto significa que, en el ejemplo con la conexión de red, debe intentar volver a abrir la conexión o notificar al usuario o algo así. Además, siempre que use catch, siempre capture solo la excepción que quiera capturar, no use declaraciones de captura amplias como catch (Exception e) eso atrapará todas las excepciones. Esto es muy importante, porque de lo contrario, accidentalmente podría detectar la excepción incorrecta y reaccionar de forma incorrecta.

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!")
}

Por qué no debería usar catch (Exception e)?

Usemos un pequeño ejemplo para mostrar por qué no debería simplemente atrapar todas las excepciones:

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

Lo que este código intenta hacer es atrapar el ArithmeticException causada por una posible división por 0. Pero también atrapa una posible NullPointerException que se arroja si a o b son null. Esto significa que podrías obtener un NullPointerException pero lo tratará como una ArithmeticException y probablemente lo haga incorrectamente. En el mejor de los casos, todavía te pierdes que había una NullPointerException. Cosas así hacen que la depuración sea mucho más difícil, así que no hagas eso.

TLDR

  1. Averiguar cuál es la causa de la excepción y corregirla, para que no arroje la excepción.
  2. Si 1. no es posible, capture la excepción específica y maneje.

    • ¡Nunca agregue un try / catch y simplemente ignore la excepción! ¡No hagas eso!
    • Nunca usar catch (Exception e), siempre atrape Excepciones específicas. Eso te ahorrará muchos dolores de cabeza.

65
2017-10-14 10:40



Para agregar a lo que Rob ha mencionado. Establecer puntos de interrupción en su aplicación permite el procesamiento paso a paso de la pila. Esto permite que el desarrollador use el depurador para ver en qué punto exacto el método está haciendo algo inesperado.

Desde que Rob usó el NullPointerException (NPE) para ilustrar algo común, podemos ayudar a eliminar este problema de la siguiente manera:

si tenemos un método que toma parámetros tales como: void (String firstName) 

En nuestro código, querríamos evaluar que firstName contiene un valor, lo haríamos así: if(firstName == null || firstName.equals("")) return;

Lo anterior nos impide usar firstName como un parámetro inseguro. Por lo tanto, al hacer comprobaciones nulas antes del procesamiento, podemos ayudar a garantizar que nuestro código se ejecute correctamente. Para ampliar un ejemplo que utiliza un objeto con métodos, podemos mirar aquí:

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

Lo anterior es el orden correcto para verificar nulos, comenzamos con el objeto base, perro en este caso, y luego comenzamos a recorrer el árbol de posibilidades para asegurarnos de que todo sea válido antes del procesamiento. Si la orden se revirtiera, una NPE podría ser lanzada y nuestro programa colapsaría.


17
2017-10-21 15:05



Hay otra característica de stacktrace ofrecida por la familia Throwable: la posibilidad de manipular información de seguimiento de pila

Comportamiento estándar:

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

Stack trace:

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)

Traza de pila manipulada:

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

Stack trace:

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



Para entender el nombre: Un seguimiento de pila es una lista de Excepciones (o puede decir una lista de "Causa por"), desde la Excepción más superficial (por ejemplo, Excepción de capa de servicio) a la más profunda (por ejemplo, Excepción de base de datos). Al igual que la razón por la que lo llamamos "pila" es porque la pila es Primero en salir (FILO), la excepción más profunda se produjo al principio, luego se generó una cadena de excepción, una serie de consecuencias, la excepción de superficie fue la última uno sucedió en el tiempo, pero lo vemos en primer lugar.

Clave 1: Una cosa complicada e importante aquí debe entenderse: la causa más profunda puede no ser la "causa raíz", porque si escribe algún "código incorrecto", puede causar una excepción debajo de la cual es más profunda que su capa. Por ejemplo, una mala consulta sql puede provocar el restablecimiento de la conexión SQLServerException en el bottem en lugar del error syndax, que puede ser justo en el medio de la pila.

-> Ubique la causa raíz en el medio es su trabajo. enter image description here

Clave 2: Otra cosa difícil pero importante es dentro de cada bloque "Causa por", la primera línea fue la capa más profunda y el primer lugar para este bloque. Por ejemplo,

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 fue llamado por Auther.java:25 que fue llamado por Bootstrap.java:14, Book.java:16 fue la causa raíz. Aquí adjunte un diagrama para ordenar la pila de rastreo en orden cronológico. enter image description here


10
2017-07-26 02:24



Solo para agregar a los otros ejemplos, hay clases internas (anidadas) que aparece con el $ firmar. Por ejemplo:

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

Dará como resultado este rastro de pila:

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



Las otras publicaciones describen qué es un seguimiento de pila, pero aún así puede ser difícil trabajar con él.

Si obtiene un seguimiento de la pila y desea rastrear la causa de la excepción, un buen punto de partida para entenderlo es usar el Consola de seguimiento de pila de Java en Eclipse. Si usa otro IDE, puede haber una función similar, pero esta es sobre Eclipse.

Primero, asegúrese de tener todas sus fuentes de Java accesibles en un proyecto de Eclipse.

Entonces en el Java perspectiva, haga clic en Consola pestaña (generalmente en la parte inferior). Si la vista de la consola no está visible, ve a la opción de menú Ventana -> Mostrar vista y seleccione Consola.

Luego, en la ventana de la consola, haga clic en el siguiente botón (a la derecha)

Consoles button

y luego seleccionar Consola de seguimiento de pila de Java de la lista desplegable.

Pegue su traza de pila en la consola. A continuación, proporcionará una lista de enlaces a su código fuente y a cualquier otro código fuente disponible.

Esto es lo que podría ver (imagen de la documentación de Eclipse):

Diagram from Eclipse documentation

La llamada a método más reciente será la parte superior de la pila, que es la línea superior (excluyendo el texto del mensaje). Bajar la pila retrocede en el tiempo. La segunda línea es el método que llama a la primera línea, etc.

Si está utilizando software de código abierto, es posible que deba descargar y adjuntar a su proyecto las fuentes si desea examinar. Descargue los jar fuente, en su proyecto, abra el Bibliotecas referenciadas carpeta para encontrar su jar para su módulo de código abierto (el que tiene los archivos de clase), luego haga clic derecho, seleccione Propiedades y coloque el frasco de origen.


5
2018-03-12 09:34