O que é rastreamento de pilha e como posso usá-lo para depurar os erros do meu aplicativo?

Oct 21 2010

Às vezes, quando executo meu aplicativo, recebo um erro semelhante a:

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)

As pessoas se referem a isso como um "rastreamento de pilha". O que é rastreamento de pilha? O que isso pode me dizer sobre o erro que está acontecendo em meu programa?


Sobre esta questão - muitas vezes eu vejo uma pergunta quando um programador novato está "recebendo um erro", e ele simplesmente cola seu rastreamento de pilha e algum bloco de código aleatório sem entender o que é rastreamento de pilha ou como eles podem usá-lo. Esta pergunta é uma referência para programadores novatos que podem precisar de ajuda para entender o valor de um rastreamento de pilha.

Respostas

623 RobHruska Oct 21 2010 at 21:52

Em termos simples, um rastreamento de pilha é uma lista das chamadas de método que o aplicativo estava no meio quando uma exceção foi lançada.

Exemplo Simples

Com o exemplo dado na pergunta, podemos determinar exatamente onde a exceção foi lançada no aplicativo. Vamos dar uma olhada no rastreamento de pilha:

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 é um rastreamento de pilha muito simples. Se começarmos no início da lista de "em ...", podemos dizer onde nosso erro aconteceu. O que estamos procurando é a chamada de método superior que faz parte de nosso aplicativo. Neste caso, é:

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

Para depurar isso, podemos abrir Book.javae olhar a linha 16, que é:

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

Isso indicaria que algo (provavelmente title) está nullno código acima.

Exemplo com uma cadeia de exceções

Às vezes, os aplicativos capturam uma Exceção e a lançam novamente como causa de outra Exceção. Isso normalmente se parece com:

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   }

Isso pode fornecer um rastreamento de pilha semelhante a:

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

O que é diferente sobre este é "Causado por". Às vezes, as exceções terão várias seções "Causado por". Para eles, você normalmente deseja encontrar a "causa raiz", que será uma das menores seções "Causada por" no rastreamento da pilha. No nosso caso, é:

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

Novamente, com essa exceção, gostaríamos de examinar a linha 22de Book.javapara ver o que pode causar o NullPointerExceptionaqui.

Exemplo mais assustador com código de biblioteca

Normalmente, os rastreamentos de pilha são muito mais complexos do que os dois exemplos acima. Aqui está um exemplo (é longo, mas demonstra vários níveis de exceções encadeadas):

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

Neste exemplo, há muito mais. O que mais nos preocupa é em procurar métodos que sejam de nosso código , que seriam qualquer coisa no com.example.myprojectpacote. A partir do segundo exemplo (acima), primeiro gostaríamos de procurar a causa raiz, que é:

Caused by: java.sql.SQLException

No entanto, todas as chamadas de método em que são código de biblioteca. Portanto, passaremos para "Causado por" acima dele e procuraremos a primeira chamada de método originada em nosso código, que é:

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

Como nos exemplos anteriores, devemos olhar MyEntityService.javaon-line 59, porque é aí que o erro se originou (este é um pouco óbvio o que deu errado, já que o SQLException indica o erro, mas o procedimento de depuração é o que estamos procurando).

82 Dakkaron Oct 14 2015 at 17:40

Estou postando esta resposta para que a primeira resposta (quando classificada por atividade) não seja simplesmente errada.

O que é um Stacktrace?

Um rastreamento de pilha é uma ferramenta de depuração muito útil. Ele mostra a pilha de chamadas (ou seja, a pilha de funções que foram chamadas até aquele ponto) no momento em que uma exceção não capturada foi lançada (ou no momento em que o rastreamento de pilha foi gerado manualmente). Isso é muito útil porque não mostra apenas onde o erro aconteceu, mas também como o programa foi parar naquele lugar do código. Isso nos leva à próxima pergunta:

O que é uma exceção?

Uma exceção é o que o ambiente de tempo de execução usa para informar que ocorreu um erro. Exemplos populares são NullPointerException, IndexOutOfBoundsException ou ArithmeticException. Cada um deles é causado quando você tenta fazer algo que não é possível. Por exemplo, um NullPointerException será lançado quando você tentar cancelar a referência de um 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.

Como devo lidar com Stacktraces / Exceptions?

Em primeiro lugar, descubra o que está causando a exceção. Experimente pesquisar no Google o nome da exceção para descobrir qual é a causa dessa exceção. Na maioria das vezes, será causado por código incorreto. Nos exemplos fornecidos acima, todas as exceções são causadas por código incorreto. Portanto, para o exemplo NullPointerException, você pode ter certeza de que anunca é nulo naquele momento. Você pode, por exemplo, inicializar aou incluir uma verificação como esta:

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

Desta forma, a linha infratora não é executada se a==null. O mesmo vale para os outros exemplos.

Às vezes, você não pode ter certeza de que não receberá uma exceção. Por exemplo, se você estiver usando uma conexão de rede em seu programa, você não pode impedir que o computador perca sua conexão com a Internet (por exemplo, você não pode impedir que o usuário desconecte a conexão de rede do computador). Nesse caso, a biblioteca de rede provavelmente lançará uma exceção. Agora você deve capturar a exceção e tratá- la. Isso significa que, no exemplo com a conexão de rede, você deve tentar reabrir a conexão ou notificar o usuário ou algo parecido. Além disso, sempre que você usar catch, sempre capture apenas a exceção que deseja capturar, não use declarações abrangentes de captura, como secatch (Exception e) isso fosse pegar todas as exceções. Isso é muito importante, porque, do contrário, você pode acidentalmente capturar a exceção errada e reagir da maneira errada.

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 que não devo usar catch (Exception e)?

Vamos usar um pequeno exemplo para mostrar por que você não deve apenas capturar todas as exceções:

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

O que este código está tentando fazer é capturar o ArithmeticExceptioncausado por uma possível divisão por 0. Mas ele também captura um possível NullPointerExceptionque é lançado se aou bfor null. Isso significa que você pode obter um, NullPointerExceptionmas irá tratá-lo como ArithmeticException e provavelmente fará a coisa errada. Na melhor das hipóteses, você ainda não percebeu que havia um NullPointerException. Coisas como essa tornam a depuração muito mais difícil, então não faça isso.

TLDR

  1. Descubra qual é a causa da exceção e corrija-a, para que ela não lance a exceção de forma alguma.
  2. Se 1. não for possível, capture a exceção específica e trate-a.

    • Nunca apenas adicione um try / catch e simplesmente ignore a exceção! Não faça isso!
    • Nunca use catch (Exception e), sempre capture exceções específicas. Isso vai lhe poupar muitas dores de cabeça.
21 Woot4Moo Oct 21 2010 at 22:05

Para acrescentar ao que Rob mencionou. Definir pontos de interrupção em seu aplicativo permite o processamento passo a passo da pilha. Isso permite que o desenvolvedor use o depurador para ver em que ponto exato o método está fazendo algo que não foi previsto.

Como Rob usou o NullPointerException(NPE) para ilustrar algo comum, podemos ajudar a remover esse problema da seguinte maneira:

se tivermos um método que leva parâmetros como: void (String firstName)

Em nosso código, gostaríamos de avaliar que firstNamecontém um valor, faríamos assim:if(firstName == null || firstName.equals("")) return;

O acima nos impede de usar firstNamecomo um parâmetro inseguro. Portanto, ao fazer verificações de nulos antes do processamento, podemos ajudar a garantir que nosso código seja executado corretamente. Para expandir um exemplo que utiliza um objeto com métodos, podemos olhar aqui:

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

A ordem acima é a correta para verificar se há nulos, começamos com o objeto base, dog neste caso, e então começamos a descer a árvore de possibilidades para ter certeza de que tudo é válido antes do processamento. Se a ordem fosse revertida, um NPE poderia ser lançado e nosso programa travaria.

15 przemekhertel Sep 17 2014 at 00:34

Há mais um recurso de rastreamento de pilha oferecido pela família Throwable - a possibilidade de manipular informações de rastreamento de pilha.

Comportamento padrão:

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

Rastreamento de pilha:

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)

Rastreamento de pilha manipulado:

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

Rastreamento de pilha:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)
15 KevinLi Jul 26 2016 at 09:24

Para entender o nome : Um rastreamento de pilha é uma lista de Exceções (ou você pode dizer uma lista de "Causa por"), da Exceção mais superficial (por exemplo, Exceção da camada de serviço) até a mais profunda (por exemplo, Exceção de banco de dados). Assim como a razão pela qual a chamamos de 'pilha' é porque a pilha é o primeiro a entrar no último a sair (FILO), a exceção mais profunda aconteceu no início, então uma cadeia de exceções foi gerada uma série de consequências, a Exceção de superfície foi a última um aconteceu com o tempo, mas nós o vemos em primeiro lugar.

Chave 1 : Uma coisa complicada e importante aqui que precisa ser entendida é: a causa mais profunda pode não ser a "causa raiz", porque se você escrever algum "código ruim", ele pode causar alguma exceção que é mais profunda do que sua camada. Por exemplo, uma consulta sql incorreta pode causar a redefinição da conexão SQLServerException no fundo em vez do erro de sindax, que pode estar apenas no meio da pilha.

-> Localize a causa raiz no meio é o seu trabalho.

Chave 2 : Outra coisa complicada, mas importante, é dentro de cada bloco "Causa por", a primeira linha era a camada mais profunda e acontecia em primeiro lugar para este bloco. Por exemplo,

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 foi chamado por Auther.java:25 que foi chamado por Bootstrap.java:14, Book.java:16 foi a causa raiz. Aqui, anexe um diagrama para classificar a pilha de rastreamento em ordem cronológica.

8 EugeneS Apr 19 2016 at 10:43

Apenas para adicionar aos outros exemplos, existem classes internas (aninhadas) que aparecem com o $sinal. Por exemplo:

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

Resultará neste rastreamento de pilha:

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)
6 rghome Mar 12 2015 at 16:34

As outras postagens descrevem o que é um rastreamento de pilha, mas ainda pode ser difícil de trabalhar.

Se você obtiver um rastreamento de pilha e desejar rastrear a causa da exceção, um bom ponto de partida para entendê-lo é usar o Java Stack Trace Console no Eclipse . Se você usar outro IDE, pode haver um recurso semelhante, mas esta resposta é sobre o Eclipse.

Primeiro, certifique-se de ter todas as fontes Java acessíveis em um projeto Eclipse.

Em seguida, na perspectiva Java , clique na guia Console (geralmente na parte inferior). Se a visualização do Console não estiver visível, vá para a opção de menu Janela -> Mostrar Visualização e selecione Console .

Em seguida, na janela do console, clique no botão a seguir (à direita)

e selecione Java Stack Trace Console na lista suspensa.

Cole o rastreamento de pilha no console. Em seguida, ele fornecerá uma lista de links para seu código-fonte e qualquer outro código-fonte disponível.

Isso é o que você pode ver (imagem da documentação do Eclipse):

A chamada de método mais recente feita será o topo da pilha, que é a linha superior (excluindo o texto da mensagem). Descendo a pilha, você volta no tempo. A segunda linha é o método que chama a primeira linha, etc.

Se você estiver usando um software de código-fonte aberto, pode ser necessário fazer o download e anexar ao seu projeto os códigos-fonte que deseja examinar. Baixe os jars de código-fonte, em seu projeto, abra a pasta Bibliotecas referenciadas para encontrar seu jar para seu módulo de código-fonte aberto (aquele com os arquivos de classe) e clique com o botão direito, selecione Propriedades e anexe o jar de código-fonte.