没有调试代理,空指针异常堆栈跟踪不可用。

61

我最近发现一个导致NullPointerException的bug。异常通过一个标准的slf4j语句被捕获和记录。以下是删节后的代码:

for(Action action : actions.getActions()) {
    try {
        context = action.execute(context);
    } catch (Exception e) {
        logger.error("...", e);
        break;
    }
}
正如您所见,没有什么花哨的东西。然而,我们所有的异常记录语句中,只有这个语句没有打印堆栈跟踪信息。 它所打印的只是消息(表示为“...”)和异常类的名称(java.lang.NullPointerException)。
由于异常的堆栈跟踪是延迟加载的,我认为可能存在某种指令重排序问题,并决定在日志语句之前调用e.getStackTrace()。但这并没有改变任何事情。
所以我决定启用调试代理重新开始。但是,因为我甚至附加到了进程,我注意到现在堆栈跟踪正在输出。因此,显然调试代理的存在导致了一些额外的调试信息变得可用。
此后,我已经修复了异常的根本原因。但是我想知道为什么在没有调试器的情况下无法使用堆栈跟踪。有人知道吗?
澄清:这不是一个记录问题。 想象一下相同的try/catch子句,但在catch中,我打印:
e.getStackTrace().length

没有调试器时,它会打印“0”,有调试器时则会打印正整数(在本例中为9)。

更多信息:这是在JDK 1.6.0_13、64位、amd64、linux 2.6.9上发生的。


2
你使用的虚拟机是什么?那种行为听起来非常奇怪。 - Michael Wiles
JDK 1.6.0_13,64位于Linux 2.6.9上。 - omerkudat
当你在try块中自己抛出'new NullPointerException()'时会发生什么? - seth
感谢您提出这个问题(以及解决方案)!我刚刚将其作为我的日志应用程序的错误报告收到,几乎要疯了...因为我的代码中确实没有错误。这解决了很多问题。 - Huxi
可能是Java中没有StackTrace的NullPointerException的重复问题。 - Christian
显示剩余2条评论
3个回答

92
通过使用JVM标志-XX:-OmitStackTraceInFastThrow,您可以禁用JVM在此使用情况下的性能优化。如果提供了此参数来禁用该标志,则将可用堆栈跟踪。
有关更多信息,请查看以下版本说明:
“服务器VM中的编译器现在为所有“cold”内置异常提供正确的堆栈回溯。出于性能原因,当抛出这样的异常几次时,方法可能会重新编译。重新编译后,编译器可以选择使用不提供堆栈跟踪的预分配异常的更快策略。要完全禁用使用预分配异常,请使用此新标志:-XX:-OmitStackTraceInFastThrow。”http://java.sun.com/j2se/1.5.0/relnotes.html

3
我们再次遇到了相同的问题,尝试了这个选项,它非常好用,比完全关闭JIT效果要好得多。谢谢! - omerkudat

20

这段代码可能在内部循环中运行,此时JIT编译器可能会将调用堆栈编译为本地代码并丢失堆栈信息。当您附加调试器时,它会禁用JIT,使信息再次可用。

其他手动异常将显示信息,因为JIT没有进行优化。

从该类源代码的第102行的注释中可以看出,这似乎有时会发生:

http://logging.apache.org/log4j/1.2/xref/org/apache/log4j/spi/LocationInfo.html


3
后来我设法确认,关闭JIT(JAVA_COMPILER=false)会导致在此异常发生时完整的堆栈跟踪信息可用。 - omerkudat

0

我可以复制这个问题,但是你的Action中会发生这种情况似乎有点奇怪。

如果您使用空数组调用setStackTrace,那么只会显示文本。

 public class Fark {
   public static void main(String[] args) {
       try {
           Fark.throwMe(args.length != 0);

       }
       catch (Exception e) {
           e.printStackTrace();
       }

   }

     public static final void throwMe(boolean arg) throws Exception{
         Exception e = new NullPointerException();
         if (arg) {
           e.setStackTrace(new StackTraceElement[0]);
         }
         throw e;
     }
 }

正在运行中...

% java Fark
java.lang.NullPointerException
        at Fark.throwMe(Fark.java:15)
        at Fark.main(Fark.java:5)

% java Fark nothing
java.lang.NullPointerException

1
是的,有人也提到过这个。但事实并非如此。 - omerkudat
如果您在try块中自己抛出NullPointerException,会发生同样的事情吗? - seth
是的,如果我手动抛出异常,它会有相同的行为。而且这只会发生在这个 try/catch 中。在其他手动抛出异常的地方,我会得到正常(即完整的堆栈)的行为。 - omerkudat

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