NullPointerException 堆栈跟踪在没有调试代理的情况下不可用

我最近发现了一个导致NullPointerException的错误。使用标准 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,64bit,amd64,linux 2.6.9上


答案 1

使用 JVM 标志 -XX:-OmitStackTraceInFastThrow,您可以禁用此用例的 JVM 性能优化。如果给定此参数,该参数禁用了该标志,则堆栈跟踪将可用。

有关详细信息,请查看以下发行说明:

“服务器 VM 中的编译器现在为所有”冷“内置异常提供正确的堆栈回溯。出于性能目的,当多次引发此类异常时,可能会重新编译该方法。重新编译后,编译器可能会使用不提供堆栈跟踪的预分配异常来选择更快的策略。若要完全禁止使用预分配的异常,请使用以下新标志:-XX:-省略StackTraceInFastThrow。http://java.sun.com/j2se/1.5.0/relnotes.html


答案 2

此代码是否有可能位于内部循环中?然后,JIT 编译器可能会将此调用堆栈编译为本机代码,从而丢失堆栈信息。然后,当您附加调试器时,它将禁用 JIT,使信息再次可用。

其他手动异常会继续显示信息,因为 JIT 未进行优化。

看起来有时其他人可能会从此类源代码第102行的注释中发生这种情况:

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


推荐