什么是堆栈跟踪,以及如何使用它来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.java
第22
行,看看在这里可能会导致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
但是它也捕获如果a
或b
为null
可能引发的NullPointerException
。 这意味着,你可能会得到一个NullPointerException
但是你会把它当作一个ArithmeticException,可能会做错误的事情。 在最好的情况下,你仍然错过有一个NullPointerException。 这样的东西使debugging更难,所以不要这样做。
TLDR
- 弄清楚什么是exception的原因,并解决它,所以它不会抛出exception。
-
如果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文档的图片):
最近的方法调用将成为堆栈的顶部,这是最上面一行(不包括消息文本)。 往下走是时光倒stream了。 第二行是调用第一行的方法等
如果您正在使用开源软件,则可能需要下载并附加到您的项目的源代码,如果您想要检查。 在你的项目中下载源代码jar文件,打开Referenced Libraries文件夹,find你的开源模块(包含类文件的jar)的jar文件,然后右键单击,selectProperties并附加源代码jar。