有时当我运行我的应用程序时,它会给我一个错误,看起来像这样:

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)

人们将其称为“堆栈跟踪”。什么是堆栈跟踪?关于程序中发生的错误,它能告诉我什么?


关于这个问题,我经常看到一个新手程序员“得到一个错误”,他们只是简单地粘贴他们的堆栈跟踪和一些随机的代码块,而不了解堆栈跟踪是什么或如何使用它。此问题旨在作为新手程序员的参考,他们可能需要帮助来理解堆栈跟踪的值。


当前回答

Throwable家族还提供了一个堆栈跟踪特性——操作堆栈跟踪信息的可能性。

标准的行为:

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

堆栈跟踪:

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)

操纵堆栈跟踪:

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

堆栈跟踪:

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

其他回答

为了补充其他示例,有一些内部(嵌套)类以$符号出现。例如:

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

将导致以下堆栈跟踪:

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)

简单来说,堆栈跟踪是抛出异常时应用程序正在执行的方法调用的列表。

简单的例子

通过问题中给出的示例,我们可以确定异常在应用程序中抛出的确切位置。让我们看一下堆栈跟踪:

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)

这是一个非常简单的堆栈跟踪。如果我们从“at…”列表的开头开始,我们可以知道错误发生在哪里。我们要找的是应用程序中最顶层的方法调用。在本例中,它是:

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

要调试这个,我们可以打开Book.java并查看第16行,它是:

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

这将表明上面代码中的某些内容(可能是title)为空。

带有异常链的示例

有时应用程序会捕获一个异常并将其作为另一个异常的原因重新抛出。这通常看起来像:

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   }

这可能会给你一个像这样的堆栈跟踪:

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

这个的不同之处在于"起因"有时异常会有多个“原因”部分。对于这些问题,您通常希望找到“根本原因”,这将是堆栈跟踪中最低的“原因”部分之一。在我们的例子中,它是:

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

同样,有了这个异常,我们想要查看Book.java的第22行,看看是什么可能导致这里的NullPointerException。

更令人生畏的库代码示例

通常,堆栈跟踪要比上面的两个示例复杂得多。下面是一个例子(它很长,但演示了几个级别的链式异常):

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

在这个例子中,有更多。我们最关心的是寻找来自代码的方法,也就是com.example.myproject包中的任何方法。从上面的第二个例子中,我们首先要向下寻找根本原因,即:

Caused by: java.sql.SQLException

但是,在该方法下的所有方法调用都是库代码。因此,我们将移动到它上面的“Caused by”,在“Caused by”块中,寻找来自我们代码的第一个方法调用,它是:

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

像前面的例子一样,我们应该查看第59行上的MyEntityService.java,因为这是错误产生的地方(这个错误有点明显,因为SQLException声明了错误,但调试过程是我们所要做的)。

补充一下罗伯提到的。在应用程序中设置断点可以逐步处理堆栈。这使开发人员能够使用调试器来查看方法在确切的哪个点上执行了未预料到的操作。

由于Rob已经使用NullPointerException (NPE)来说明一些常见的问题,我们可以通过以下方式帮助消除这个问题:

如果我们有一个接受参数的方法,比如:

在我们的代码中,我们希望计算firstName包含一个值,我们将这样做:if(firstName == null || firstName.equals("")) return;

以上可以防止我们使用firstName作为不安全的参数。因此,通过在处理之前进行空检查,我们可以帮助确保我们的代码能够正常运行。要展开一个利用对象的方法的例子,我们可以看这里:

If (dog == null || dog. If (dog == null || dog. If)firstName == null)返回;

以上是检查null的正确顺序,我们从基本对象开始,在本例中是dog,然后开始沿着可能性树向下走,以确保在处理之前所有内容都是有效的。如果顺序颠倒,可能会抛出一个NPE,我们的程序将崩溃。

Throwable家族还提供了一个堆栈跟踪特性——操作堆栈跟踪信息的可能性。

标准的行为:

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

堆栈跟踪:

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)

操纵堆栈跟踪:

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

堆栈跟踪:

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

To understand the name: A stack trace is a a list of Exceptions( or you can say a list of "Cause by"), from the most surface Exception(e.g. Service Layer Exception) to the deepest one (e.g. Database Exception). Just like the reason we call it 'stack' is because stack is First in Last out (FILO), the deepest exception was happened in the very beginning, then a chain of exception was generated a series of consequences, the surface Exception was the last one happened in time, but we see it in the first place.

关键一:这里需要理解的一个棘手而重要的事情是:最深层的原因可能不是“根本原因”,因为如果你写了一些“糟糕的代码”,它可能会导致一些比它的层更深的异常。例如,一个糟糕的sql查询可能会导致SQLServerException连接在底部重置,而不是syndax错误,这可能只是在堆栈的中间。

->在中间找到根本原因是你的工作。

关键2:另一个棘手但重要的事情是,在每个“Cause by”块中,第一行是最深的层,发生在该块的第一个位置。例如,

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是由Auther.java:25调用的,Bootstrap.java:14调用的,Book.java:16是根本原因。 这里附上一个按时间顺序对跟踪堆栈进行排序的图表。