Java中没有StackTrace的NullPointerException

440

我们的Java代码遇到了NullPointerException的情况,但是当我尝试记录StackTrace(实际上调用了Throwable.printStackTrace())时,我得到的仅仅是:

java.lang.NullPointerException

有其他人遇到过这个问题吗?我尝试谷歌搜索“java空指针空栈跟踪”,但没有找到类似的内容。


这是什么上下文?是否涉及多个线程?我在尝试获取SwingWorker中异常的堆栈跟踪时遇到了问题。 - Michael Myers
这里没有涉及到线程,只是普通的Java。 - Edward Shtern
1
@Bozho - 不是的 - 我还不确定如何重现空指针异常。 - Edward Shtern
1
相关:https://dev59.com/P3NA5IYBdhLWcg3wI6R4 - Joshua Goldberg
有关 -XX: -OmitStackTraceInFastThrow 的更多信息,请参见 https://dev59.com/eG445IYBdhLWcg3w3N6z。 - Vadzim
12个回答

565

你可能正在使用HotSpot JVM(最初由Sun Microsystems开发,后被Oracle收购,是OpenJDK的一部分),其进行了大量的优化。要获取堆栈跟踪信息,您需要向JVM传递以下选项:

-XX:-OmitStackTraceInFastThrow
优化在于,当第一次发生异常(通常是 NullPointerException)时,将打印完整的堆栈跟踪,并且JVM会记住堆栈跟踪(或者可能只是代码位置)。当该异常足够频繁地发生时,不再打印堆栈跟踪,以实现更好的性能和避免将相同的堆栈跟踪记录到日志中。要查看这在 Hotspot JVM 中是如何实现的,请 获取它的副本 并搜索全局变量 OmitStackTraceInFastThrow。我上次看代码是在2019年,当时它在文件 graphKit.cpp 中。

1
谢谢你的提示。你知道传递这个选项是否有任何隐藏的陷阱吗(只要我的应用程序不抛出大量异常,它似乎相当无害)? - Edward Shtern
据我所知,没有任何隐藏的陷阱。当您查看Hotspot源代码时,可以看到此选项仅在一个地方使用(graphKit.cpp)。而且,在我看来,这看起来很好。 - Roland Illig
37
我觉得我应该补充额外的信息,当堆栈跟踪被优化掉时,至少已处理过一次:http://jawspeak.com/2010/05/26/hotspot-caused-exceptions-to-lose-their-stack-traces-in-production-and-the-fix/。 - sharakan
1
我正在运行一个OpenJDK JVM,版本为1.8.0u171(Debian 9),它似乎也接受“-XX:-OmitStackTraceInFastThrow”标志。我还没有确认是否是因为这个原因我也无法打印堆栈跟踪(例如使用“e.printStackTrace”),但这似乎非常可能。我已经扩展了答案以反映这一发现。 - Chris W.
在我们的情况下,前125个异常都有堆栈跟踪,然后在日志文件的3个轮换中的其余异常都没有。这个答案对于找到罪魁祸首非常有帮助。 - sukhmel

69

正如您在评论中提到的一样,您正在使用 log4j。我偶然发现了一个我写错的地方

LOG.error(exc);

不是典型的

LOG.error("Some informative message", e);

由于懒惰或者只是没有考虑到,代码行为表现与你的期望不同。实际上,logger API将第一个参数作为对象(Object)而不是字符串(string)处理,并调用该参数的toString()方法。因此,它并不会打印出漂亮的堆栈跟踪信息,而是直接打印出toString()返回的内容,这在NPE的情况下是相当无用的。

也许这就是你正在遇到的问题?


+1:这可以解释所描述的行为,而且你不是唯一一个发现了这个问题的人 :) - Peter Lang
4
我们实际上有一个标准政策,从不使用第一种形式(LOG.error(exc);) - 我们总是使用两个参数的签名,这样我们可以在日志中添加一些描述性语句,而不仅仅是一个原始的堆栈跟踪。 - Edward Shtern
6
当然,但政策并不意味着总是被正确执行!我想值得一提。 - Steven Schlansker
真的,但在这种情况下是这样的;-) - Edward Shtern

36
我们过去曾经见过这种相同的行为。事实证明,由于某种疯狂的原因,如果在代码的同一位置多次发生NullPointerException,一段时间后使用Log.error(String, Throwable)将不再包含完整的堆栈跟踪。
尝试在日志中进一步查找。你可能会找到罪魁祸首。
编辑:这个bug听起来很相关,但是它在很久以前就被修复了,可能不是原因。

3
这个错误已经被修复了,但是仍需要使用-XX: -OmitStackTraceInFastThrow标志来解决性能优化问题。 - Joshua Goldberg
最近我经常看到这个问题。有什么线索可以说明是什么原因导致的,或者如何解决它?日志系统可能已经运行了几天,实际原因已经轮换出去了,更不用说繁琐的搜索了... - Pawel Veselov
5
Pawel,你尝试过 Joshua 建议的 -XX:-OmitStackTraceInFastThrow JVM 标志吗?请参考 https://dev59.com/P3NA5IYBdhLWcg3wI6R4#2070568。 - Matt Solnit
1
这就是我们的全部内容。谢谢。 - Andrew Cheong

24

以下是解释:Hotspot caused exceptions to lose their stack traces in production – and the fix

我已经在 Mac OS X 上进行了测试

  • java 版本 "1.6.0_26"
  • Java(TM) SE 运行时环境 (build 1.6.0_26-b03-383-11A511)
  • Java HotSpot(TM) 64 位服务器虚拟机 (build 20.1-b02-383, 混合模式)

Object string = "abcd";
int i = 0;
while (i < 12289) {
    i++;
    try {
        Integer a = (Integer) string;
    } catch (Exception e) {
        e.printStackTrace();
    }
}
针对这段代码片段,12288 次迭代(加上频率)似乎是 JVM 决定使用预分配异常的限制...

4
回溯机器链接:https://web.archive.org/web/20190911113910/http://jawspeak.com/2010/05/26/hotspot-caused-exceptions-to-lose-their-stack-traces-in-production-and-the-fix/ 请仅返回翻译后的文本。 - aff

11

exception.toString无法提供StackTrace,它只返回异常信息本身。

a short description of this throwable. The result is the concatenation of:

* the name of the class of this object
* ": " (a colon and a space)
* the result of invoking this object's getLocalizedMessage() method

请使用exception.printStackTrace代替,以输出堆栈跟踪信息。

1
抱歉,在我原始的帖子中说错了。我正在通过Log4J记录这些内容,它确实使用printStackTrace()。 - Edward Shtern
1
你尝试过使用 getStackTrace() 来确保问题不是出在你的日志记录器上吗? - Peter Lang
1
如果您正在使用log4j,请确保将异常作为参数之一发送到日志记录方法中。我会发布一个答案并说明。 - Ravi Wallau
@raviaw 说得好!@Edward Shtern:你能确认你绝对使用了log4j方法的2个参数形式吗?我知道你在下面的回答中提到这是公司的政策,但你确定在这种情况下你遵循了政策吗? - KarstenF
这可能是一个冒险的尝试,但异常是否源自某些第三方代码是有可能的吗?也许它是一个(编写不良的)异常包装器,其toString()仅返回包装异常的类名,并未提供底层堆栈跟踪。尝试在catch块中添加类似logger.info("Exception class = " + exc.class.getCanonicalName())的内容,看看你会得到什么。 - KarstenF

5

替代建议 - 如果您使用Eclipse,可以在NullPointerException本身上设置断点(在调试透视图中,转到“断点”选项卡,然后单击带有!的小图标)。

勾选“已捕获”和“未捕获”选项 - 现在当您触发NPE时,您将立即停止,并且您可以逐步检查并查看它是如何处理的以及为什么您没有获取堆栈跟踪。


2

toString() 只返回异常名称和可选消息。我建议调用

exception.printStackTrace()

如果你需要转储消息,或者需要详细信息:

 StackTraceElement[] trace = exception.getStackTrace()

请看上面 - 我说错了 - 我正在使用printStackTrace()。 - Edward Shtern

2

虚拟机会对已经被抛出多次的异常停止输出堆栈跟踪信息,这是C2编译器中的一种优化。

根据OpenJDK源代码显示,此优化适用于以下异常:

-NullPointerException(空指针异常) -ArithmeticException(算术异常) -ArrayIndexOutOfBoundsException(数组越界异常) -ArrayStoreException(数组存储异常) -ClassCastException(类转换异常)


1
当您在项目中使用AspectJ时,可能会发生某些方面隐藏其部分堆栈跟踪的情况。例如,今天我遇到了以下问题:
java.lang.NullPointerException:
  at com.company.product.MyTest.test(MyTest.java:37)

这个堆栈跟踪是在通过Maven的surefire运行测试时打印出来的。
另一方面,当在IntelliJ中运行测试时,会打印出不同的堆栈跟踪:
java.lang.NullPointerException
  at com.company.product.library.ArgumentChecker.nonNull(ArgumentChecker.java:67)
  at ...
  at com.company.product.aspects.CheckArgumentsAspect.wrap(CheckArgumentsAspect.java:82)
  at ...
  at com.company.product.MyTest.test(MyTest.java:37)

1
这将输出异常,仅用于调试,您应该更好地处理异常。
import java.io.PrintWriter;
import java.io.StringWriter;
    public static String getStackTrace(Throwable t)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接