什么是堆栈跟踪?如何使用它来调试应用程序错误?
有时,当我运行应用程序时,它会给我一个如下所示的错误:
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)
人们将此称为“堆栈跟踪”。 什么是堆栈跟踪?它可以告诉我有关程序中发生的错误的哪些信息?
关于这个问题 - 我经常看到新手程序员“遇到错误”的问题,他们只是粘贴堆栈跟踪和一些随机代码块,而不了解堆栈跟踪是什么或他们如何使用它。这个问题旨在为可能需要帮助理解堆栈跟踪值的新手程序员提供参考。
Sometimes when I run my application it gives me an error that looks like:
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)
People have referred to this as a "stack trace". What is a stack trace? What can it tell me about the error that's happening in my program?
About this question - quite often I see a question come through where a novice programmer is "getting an error", and they simply paste their stack trace and some random block of code without understanding what the stack trace is or how they can use it. This question is intended as a reference for novice programmers who might need help understanding the value of a stack trace.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
简单来说,堆栈跟踪是抛出异常时应用程序正在进行的方法调用的列表。
简单示例
通过问题中给出的示例,我们可以准确确定应用程序中抛出异常的位置。让我们看一下堆栈跟踪:
这是一个非常简单的堆栈跟踪。如果我们从“at ...”列表的开头开始,我们就可以知道错误发生在哪里。我们正在寻找的是属于我们应用程序一部分的topmost方法调用。在本例中,它是:
要调试它,我们可以打开
Book.java
并查看第16
行,即:这将表明某些内容(可能是
title
) 在上面的代码中为null
。异常链示例
有时,应用程序会捕获一个异常,然后将其重新抛出,作为另一个异常的原因。这通常看起来像:
这可能会给你一个看起来像这样的堆栈跟踪:
这个的不同之处在于“引起的”。有时异常会有多个“原因”部分。对于这些,您通常希望找到“根本原因”,这将是堆栈跟踪中最低的“原因”部分之一。在我们的例子中,它是:
同样,除了这个异常,我们希望查看
Book.java
的第22
行,看看是什么可能导致NullPointerException这里。
带有库代码的更令人畏惧的示例
通常堆栈跟踪比上面的两个示例复杂得多。这是一个示例(虽然很长,但演示了多个级别的连锁异常):
在这个示例中,还有更多内容。我们最关心的是寻找来自我们的代码的方法,这些方法可以是
com.example.myproject
包中的任何内容。从上面的第二个示例中,我们首先要向下查找根本原因,即:但是,其下的所有方法调用都是库代码。因此,我们将向上移动到其上方的“Caused by”,并在该“Caused by”块中查找源自我们的第一个方法调用代码,即:
像前面的示例一样,我们应该查看第
59
行上的MyEntityService.java
,因为这就是此错误的根源(这个有点很明显出了什么问题,因为 SQLException 指出了错误,但调试过程正是我们所追求的)。In simple terms, a stack trace is a list of the method calls that the application was in the middle of when an Exception was thrown.
Simple Example
With the example given in the question, we can determine exactly where the exception was thrown in the application. Let's have a look at the stack trace:
This is a very simple stack trace. If we start at the beginning of the list of "at ...", we can tell where our error happened. What we're looking for is the topmost method call that is part of our application. In this case, it's:
To debug this, we can open up
Book.java
and look at line16
, which is:This would indicate that something (probably
title
) isnull
in the above code.Example with a chain of exceptions
Sometimes applications will catch an Exception and re-throw it as the cause of another Exception. This typically looks like:
This might give you a stack trace that looks like:
What's different about this one is the "Caused by". Sometimes exceptions will have multiple "Caused by" sections. For these, you typically want to find the "root cause", which will be one of the lowest "Caused by" sections in the stack trace. In our case, it's:
Again, with this exception we'd want to look at line
22
ofBook.java
to see what might cause theNullPointerException
here.More daunting example with library code
Usually stack traces are much more complex than the two examples above. Here's an example (it's a long one, but demonstrates several levels of chained exceptions):
In this example, there's a lot more. What we're mostly concerned about is looking for methods that are from our code, which would be anything in the
com.example.myproject
package. From the second example (above), we'd first want to look down for the root cause, which is:However, all the method calls under that are library code. So we'll move up to the "Caused by" above it, and in that "Caused by" block, look for the first method call originating from our code, which is:
Like in previous examples, we should look at
MyEntityService.java
on line59
, because that's where this error originated (this one's a bit obvious what went wrong, since the SQLException states the error, but the debugging procedure is what we're after).什么是堆栈跟踪?
堆栈跟踪是一个非常有用的调试工具。它显示抛出未捕获的异常时(或手动生成堆栈跟踪时)的调用堆栈(即,到该点调用的函数堆栈)。这非常有用,因为它不仅显示错误发生的位置,还显示程序如何在代码的该位置结束。
这就引出了下一个问题:
什么是异常?
异常是运行时环境用来告诉您发生了错误的信息。常见的例子有 NullPointerException、IndexOutOfBoundsException 或 ArithmeticException。这些都是当你尝试做一些不可能的事情时引起的。例如,当您尝试取消引用空对象时,将抛出 NullPointerException:
我应该如何处理堆栈跟踪/异常?
首先,找出导致异常的原因。尝试通过谷歌搜索异常的名称来找出该异常的原因。大多数时候这是由错误的代码引起的。在上面给出的示例中,所有异常都是由不正确的代码引起的。因此,对于 NullPointerException 示例,您可以确保当时
a
永远不会为 null。例如,您可以初始化a
或包含如下检查:这样,如果
a==null
则不会执行有问题的行。其他例子也是如此。有时您无法确保不会出现异常。例如,如果您在程序中使用网络连接,则无法阻止计算机失去其互联网连接(例如,您无法阻止用户断开计算机的网络连接)。在这种情况下,网络库可能会抛出异常。现在您应该捕获异常并处理它。这意味着,在网络连接的示例中,您应该尝试重新打开连接或通知用户或类似的事情。另外,每当您使用 catch 时,始终只捕获您想要捕获的异常,不要使用像
catch (Exception e)
这样会捕获所有异常的广泛 catch 语句。这非常重要,因为否则您可能会意外捕获错误的异常并以错误的方式做出反应。为什么我不应该使用
catch (Exception e)
?让我们用一个小例子来说明为什么不应该只捕获所有异常:
这段代码试图做的是捕获可能由 0 除引起的 ArithmeticException。但它也会捕获在
a
或b< 时抛出的可能的
NullPointerException
。 /code> 为null
。这意味着,您可能会收到 NullPointerException,但您会将其视为 ArithmeticException,并且可能会做错误的事情。在最好的情况下,您仍然会错过 NullPointerException。类似的事情会让调试变得更加困难,所以不要这样做。TLDR
catch (Exception e)
,始终捕获特定的异常。这将为您省去很多麻烦。What is a Stacktrace?
A stacktrace is a very helpful debugging tool. It shows the call stack (meaning, the stack of functions that were called up to that point) at the time an uncaught exception was thrown (or the time the stacktrace was generated manually). This is very useful because it doesn't only show you where the error happened, but also how the program ended up in that place of the code.
This leads over to the next question:
What is an Exception?
An Exception is what the runtime environment uses to tell you that an error occurred. Popular examples are NullPointerException, IndexOutOfBoundsException or ArithmeticException. Each of these are caused when you try to do something that is not possible. For example, a NullPointerException will be thrown when you try to dereference a Null-object:
How should I deal with Stacktraces/Exceptions?
At first, find out what is causing the Exception. Try googling the name of the exception to find out what the cause of that exception is. Most of the time it will be caused by incorrect code. In the given examples above, all of the exceptions are caused by incorrect code. So for the NullPointerException example you could make sure that
a
is never null at that time. You could, for example, initialisea
or include a check like this one:This way, the offending line is not executed if
a==null
. Same goes for the other examples.Sometimes you can't make sure that you don't get an exception. For example, if you are using a network connection in your program, you cannot stop the computer from loosing it's internet connection (e.g. you can't stop the user from disconnecting the computer's network connection). In this case the network library will probably throw an exception. Now you should catch the exception and handle it. This means, in the example with the network connection, you should try to reopen the connection or notify the user or something like that. Also, whenever you use catch, always catch only the exception you want to catch, do not use broad catch statements like
catch (Exception e)
that would catch all exceptions. This is very important, because otherwise you might accidentally catch the wrong exception and react in the wrong way.Why should I not use
catch (Exception e)
?Let's use a small example to show why you should not just catch all exceptions:
What this code is trying to do is to catch the
ArithmeticException
caused by a possible division by 0. But it also catches a possibleNullPointerException
that is thrown ifa
orb
arenull
. This means, you might get aNullPointerException
but you'll treat it as an ArithmeticException and probably do the wrong thing. In the best case you still miss that there was a NullPointerException. Stuff like that makes debugging much harder, so don't do that.TLDR
catch (Exception e)
, always catch specific Exceptions. That will save you a lot of headaches.对罗布提到的内容进行补充。在应用程序中设置断点可以逐步处理堆栈。这使得开发人员能够使用调试器来查看该方法在哪个具体点执行了未预料到的操作。
由于 Rob 使用
NullPointerException
(NPE) 来说明一些常见问题,因此我们可以通过以下方式帮助消除此问题:如果我们有一个采用以下参数的方法:
void (String firstName)
在我们的代码中,我们想要评估
firstName
是否包含一个值,我们会这样做:if(firstName == null || firstName.equals(" ")) return;
上面的内容阻止我们使用
firstName
作为不安全的参数。因此,通过在处理之前进行空检查,我们可以帮助确保我们的代码正常运行。要扩展使用带有方法的对象的示例,我们可以在这里查看:if(dog == null ||dog.firstName == null) return;
以上是检查 null 的正确顺序,我们从基本对象(本例中为狗)开始,然后开始沿着可能性树向下走,以确保在处理之前一切都有效。如果顺序颠倒,可能会抛出 NPE,并且我们的程序将崩溃。
To add on to what Rob has mentioned. Setting break points in your application allows for the step-by-step processing of the stack. This enables the developer to use the debugger to see at what exact point the method is doing something that was unanticipated.
Since Rob has used the
NullPointerException
(NPE) to illustrate something common, we can help to remove this issue in the following manner:if we have a method that takes parameters such as:
void (String firstName)
In our code we would want to evaluate that
firstName
contains a value, we would do this like so:if(firstName == null || firstName.equals("")) return;
The above prevents us from using
firstName
as an unsafe parameter. Therefore by doing null checks before processing we can help to ensure that our code will run properly. To expand on an example that utilizes an object with methods we can look here:if(dog == null || dog.firstName == null) return;
The above is the proper order to check for nulls, we start with the base object, dog in this case, and then begin walking down the tree of possibilities to make sure everything is valid before processing. If the order were reversed a NPE could potentially be thrown and our program would crash.
理解名字:堆栈跟踪是一个异常列表(或者你可以说“原因”列表),从最表面的异常(例如服务层异常)到最深的异常(例如数据库异常)。就像我们之所以称其为“栈”,是因为栈是先进后出(FILO)的,最深层的异常在一开始就发生了,然后产生了一系列的异常链,产生了一系列的后果,表面的异常是最后一个事情及时发生,但我们首先看到了它。
关键1:这里需要理解的一个棘手且重要的事情是:最深层的原因可能不是“根本原因”,因为如果你编写一些“糟糕的代码”,它可能会导致下面的一些异常它比它的层更深。例如,错误的 sql 查询可能会导致底部的 SQLServerException 连接重置,而不是语法错误,后者可能只是在堆栈的中间。
-> 找到中间的根本原因就是你的工作。
关键 2:另一个棘手但重要的事情是在每个“原因”块内,第一行是最深层,并且发生在该块的第一位。例如,
Book.java:16 被 Auther.java:25 调用,Auther.java:25 被 Bootstrap.java:14 调用,Book.java:16 是根本原因。
这里附上一个图表,按时间顺序对跟踪堆栈进行排序。
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.
Key 1:A tricky and important thing here need to be understand is : the deepest cause may not be the "root cause", because if you write some "bad code", it may cause some exception underneath which is deeper than its layer. For example, a bad sql query may cause SQLServerException connection reset in the bottem instead of syndax error, which may just in the middle of the stack.
-> Locate the root cause in the middle is your job.
Key 2:Another tricky but important thing is inside each "Cause by" block, the first line was the deepest layer and happen first place for this block. For instance,
Book.java:16 was called by Auther.java:25 which was called by Bootstrap.java:14, Book.java:16 was the root cause.
Here attach a diagram sort the trace stack in chronological order.
Throwable 系列还提供了一项堆栈跟踪功能 - 可以操作堆栈跟踪信息。
标准行为:
堆栈跟踪:
操作堆栈跟踪:
堆栈跟踪:
There is one more stacktrace feature offered by Throwable family - the possibility to manipulate stack trace information.
Standard behavior:
Stack trace:
Manipulated stack trace:
Stack trace:
只是为了添加到其他示例中,有一些带有
$
符号的内部(嵌套)类。例如:将产生以下堆栈跟踪:
Just to add to the other examples, there are inner(nested) classes that appear with the
$
sign. For example:Will result in this stack trace:
其他帖子描述了堆栈跟踪是什么,但它仍然很难使用。
如果您获得堆栈跟踪并想要跟踪异常的原因,理解它的一个好的起点是使用 Eclipse 中的Java Stack Trace Console。如果您使用其他 IDE,可能会有类似的功能,但这个答案是关于 Eclipse 的。
首先,确保您可以在 Eclipse 项目中访问所有 Java 源代码。
然后在Java 透视图中,单击Console 选项卡(通常位于底部)。如果控制台视图不可见,请转到菜单选项“窗口 ->”显示视图并选择控制台。
然后在控制台窗口中,单击以下按钮(右侧)
,
然后选择Java Stack从下拉列表中选择跟踪控制台。
将堆栈跟踪粘贴到控制台中。然后,它将提供指向您的源代码和任何其他可用源代码的链接列表。
例如,如果我们有这个程序:
您将得到以下堆栈跟踪:
最近进行的方法调用(以及导致异常的方法)将是以下方法的顶部堆栈,即顶行(不包括错误消息文本)。在本例中,即为
trimmedLength
方法。沿着堆栈往下走就会回到过去。第二行是调用第一行的方法,依此类推。如果您使用开源软件,如果您想检查,可能需要下载源代码并将其附加到您的项目中。下载源 jar,在您的项目中,打开“参考库”文件夹,找到您的开源模块的 jar(包含类文件的),然后右键单击,选择“属性” em> 并附加源 jar。
The other posts describe what a stack trace is, but it can still be hard to work with.
If you get a stack trace and want to trace the cause of the exception, a good start point in understanding it is to use the Java Stack Trace Console in Eclipse. If you use another IDE there may be a similar feature, but this answer is about Eclipse.
First, ensure that you have all of your Java sources accessible in an Eclipse project.
Then in the Java perspective, click on the Console tab (usually at the bottom). If the Console view is not visible, go to the menu option Window -> Show View and select Console.
Then in the console window, click on the following button (on the right)
and then select Java Stack Trace Console from the drop-down list.
Paste your stack trace into the console. It will then provide a list of links into your source code and any other source code available.
For example, if we had this program:
You would get this stack trace:
The most recent method call made (and the one that caused the exception) will be the top of the stack, which is the top line (excluding the error message text). In this case, that is the
trimmedLength
method. Going down the stack goes back in time. The second line is the method that calls the first line, etc.If you are using open-source software, you might need to download and attach to your project the sources if you want to examine. Download the source jars, in your project, open the Referenced Libraries folder to find your jar for your open-source module (the one with the class files) then right click, select Properties and attach the source jar.