什么是堆栈跟踪,以及如何使用它来debugging我的应用程序错误?

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

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) 

人们称之为“堆栈轨迹”。 什么是堆栈跟踪? 什么可以告诉我关于我的程序中发生的错误?


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

简而言之, 堆栈跟踪是应用程序在抛出exception时的方法调用列表。

简单的例子

通过问题中给出的例子,我们可以确定在应用程序中抛出exception的位置。 让我们来看看堆栈跟踪:

 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) 

为了debugging这个,我们可以打开Book.java ,看看第16行,它是:

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

这将表明在上面的代码中有些东西(可能是title )是null的。

一连串的例外

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

 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 

这一个有什么不同是“引起的”。 有时例外会有多个“由…引起”部分。 对于这些,通常需要find“根本原因”,这将是堆栈跟踪中最低的“由…引起”部分之一。 在我们的情况下,它是:

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

同样,有了这个例外,我们希望看看Book.java22行,看看在这里可能会导致NullPointerException

库代码更令人生畏的例子

通常堆栈轨迹比上面两个例子复杂得多。 这是一个例子(这是一个很长的例子,但是演示了几个级别的链接exception):

 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 

但是,下面的所有方法调用都是库代码。 因此,我们将移到上面的“由…引起”,并查找来自我们的代码的第一个方法调用,即:

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

和前面的例子一样,我们应该看59行上的MyEntityService.java ,因为这是错误发生的地方(这个错误有点显而易见,因为SQLException声明错误,但是debugging过程就是我们所要做的) 。

我发布这个答案,所以最上面的答案(按活动sorting时)不是一个只是明显错误的答案。

什么是Stacktrace?

stacktrace是一个非常有用的debugging工具。 它显示了在未捕获的exception被抛出时(或手动生成堆栈跟踪的时间)的调用堆栈(意思是调用该点的函数堆栈)。 这是非常有用的,因为它不仅告诉你错误发生在哪里,而且程序如何在代码的这个地方结束。 这导致了下一个问题:

什么是例外?

一个exception是运行时环境用来告诉你发生了一个错误。 stream行的例子是NullPointerException,IndexOutOfBoundsException或ArithmeticException。 当你尝试做一些不可能的事情时,这些都是造成的。 例如,当您尝试取消引用空对象时,将引发NullPointerException:

 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. 

我应该如何处理Stacktraces / Exceptions?

首先,找出是什么导致exception。 试着googleing这个exception的名字来找出这个exception是什么原因造成的。 大部分时间会由不正确的代码引起。 在上面给出的例子中,所有的例外都是由不正确的代码引起的。 因此,对于NullPointerException示例,您可以确保a当前不会为空。 例如,您可以初始化a或包含这样一个检查:

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

这样,如果a==null ,有问题的行不执行。 其他例子也一样。

有时你不能确定你没有得到一个例外。 例如,如果您在程序中使用networking连接,则无法停止计算机的互联网连接(例如,您不能阻止用户断开计算机的networking连接)。 在这种情况下,networking库可能会引发exception。 现在你应该捕捉exception并处理它。 这意味着,在networking连接的例子中,您应该尝试重新打开连接或通知用户或类似的东西。 另外,每当你使用catch的时候,总是只捕获你想要捕获的exception, 不要使用catch (Exception e)这样的广泛的catch语句来捕获所有exception。 这是非常重要的,否则你可能会意外地发现错误的exception,并以错误的方式作出反应。

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

为什么我不应该使用catch (Exception e)

我们用一个小例子来说明为什么你不应该只捕获所有的exception:

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

这段代码试图做的是捕获由可能的除数0引起的ArithmeticException但是它也捕获如果abnull可能引发的NullPointerException 。 这意味着,你可能会得到一个NullPointerException但是你会把它当作一个ArithmeticException,可能会做错误的事情。 在最好的情况下,你仍然错过有一个NullPointerException。 这样的东西使debugging更难,所以不要这样做。

TLDR

  1. 弄清楚什么是exception的原因,并解决它,所以它不会抛出exception。
  2. 如果1.不可能,则捕获特定exception并处理它。

    • 不要只是添加一个try / catch,然后忽略这个exception! 不要这样做!
    • 永远不要使用catch (Exception e) ,总是捕获特定的exception。 这将为您节省很多头痛。

添加到Rob提到的内容。 在应用程序中设置断点允许逐步处理堆栈。 这使得开发人员可以使用debugging器来查看该方法正在做什么的确切点。

由于Rob使用了NullPointerException(NPE)来说明一些常见的情况,我们可以通过以下方式帮助解决这个问题:

如果我们有一个方法使用如下参数: void (String firstName)

在我们的代码中,我们想要评估firstName包含一个值,我们会这样做: if(firstName == null || firstName.equals("")) return;

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

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

以上是检查空值的正确顺序,我们从基础对象开始,在这种情况下,开始走狗的可能性树,以确保在处理之前一切都是有效的。 如果顺序颠倒了,NPE可能会被抛出,我们的程序会崩溃。

Throwable系列提供了另外一个堆栈跟踪function – 可以操作堆栈跟踪信息。

标准行为:

 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) 

要理解名称 :堆栈跟踪是从最表面的exception(例如,服务层exception)到最深的exception(例如,数据库exception)的exception列表(或者可以说“原因依据”的列表)。 就像我们称之为“堆栈”的原因是因为堆栈是最后一个输出(FILO),最深的exception发生在最开始,然后一连串的exception产生了一系列的后果,表面exception是最后一个一个发生在一个时间,但我们首先看到它。

重点1 :这里需要了解的一个棘手而重要的事情是:最深的原因可能不是“根本原因”,因为如果你写了一些“不好的代码”,可能会造成一些比它更深的exception。 例如,一个错误的sql查询可能会导致SQLServerException连接重置在本体而不是syndax错误,这可能只是在堆栈的中间。

– > find中间的根本原因是你的工作。 在这里输入图像描述

关键2 :另一个棘手但重要的事情是在每个“原因”块内,第一行是最深层,并发生在这个块的第一位。 例如,

 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是根本原因。 这里附上一个图表,按时间顺序排列跟踪堆栈。 在这里输入图像描述

只要添加到其他示例中,就有使用$ sign显示的内部(嵌套)类 。 例如:

 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的原因,理解它的一个好的起点是在Eclipse中使用Java堆栈跟踪控制台 。 如果您使用其他IDE,可能会有类似的function,但是这个答案是关于Eclipse的。

首先,确保在Eclipse项目中可以访问所有Java源代码。

然后在Java透视图中,单击“ 控制台”选项卡(通常位于底部)。 如果控制台视图不可见,请转至菜单选项窗口 – >显示视图,然后select控制台

然后在控制台窗口中,点击下面的button(在右边)

控制台按钮

然后从下拉列表中selectJava Stack Trace Console

将你的堆栈跟踪粘贴到控制台中。 然后它会提供一个链接列表到您的源代码和任何其他可用的源代码。

这是你可能看到的(来自Eclipse文档的图片):

Eclipse文档中的图表

最近的方法调用将成为堆栈的顶部,这是最上面一行(不包括消息文本)。 往下走是时光倒stream了。 第二行是调用第一行的方法等

如果您正在使用开源软件,则可能需要下载并附加到您的项目的源代码,如果您想要检查。 在你的项目中下载源代码jar文件,打开Referenced Libraries文件夹,find你的开源模块(包含类文件的jar)的jar文件,然后右键单击,selectProperties并附加源代码jar。