【问题标题】:Lambda stack trace missing when using NativeMethodAccessor instead of GeneratedMethodAccessor使用 NativeMethodAccessor 而不是 GeneratedMethodAccessor 时缺少 Lambda 堆栈跟踪
【发布时间】:2019-12-26 21:29:39
【问题描述】:

几天前,我收到了一张NullPointerException 的支持票:

com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract com.redacted.SalesResponsePagination com.redacted.StatisticsService.findSalesData(com.redacted.ConfStats) throws com.redacted.AsyncException' threw an unexpected exception: java.lang.NullPointerException
    at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:389)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
    at ... (typical GWT + Tomcat stacktrace)
Caused by: java.lang.NullPointerException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
    at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
    at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source)
    at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
    at java.util.stream.ReferencePipeline.forEach(Unknown Source)
    at com.redacted.StatisticsControllerImpl.replacePrices(StatisticsControllerImpl.java:310)
    at com.redacted.StatisticsControllerImpl.findSalesData(StatisticsControllerImpl.java:288)
    at com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    at sun.reflect.GeneratedMethodAccessor752.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:561)
    ... 33 more
Caused by: java.lang.NullPointerException
    at com.redacted.StatisticsControllerImpl.lambda$replacePrices$27(StatisticsControllerImpl.java:317)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
    at ... (typical stream.forEach stacktrace)

现在,这很容易,因为NPE 的确切行号一目了然;我所要做的就是去StatisticsControllerImpl.java:317

    salesResponsePagination.getSalesResponses().parallelStream()
            .peek(sr -> /*...*/)
            .filter(sr -> /*...*/)
/*310*/     .forEach(sr -> {
                final List<CartElement> sentCEs = DaoService.getCartElementDAO().getSentCEs(/*...*/);
                if (sentCEs != null && !sentCEs.isEmpty() && sentCEs.get(0) != null) {
                    final CartElement ce = sentCEs.get(0);
                    // some more non-NPE lines...
/*317*/             if (sr.getCurrency().equals(ce.getPurchaseCurrency()) && sr.getPrice().equals(ce.getPurchasePrice().intValue()) && !ce.getCurrency().equals(ce.getPurchaseCurrency())) {
                        // Some currency exchanging
                    }
                    // Etcetera (about 12 lines more)
            });

并将.equals() 调用替换为Object.equals() 以避免NPE(后来调查了某些销售以NULL 价格或货币注册的原因)。测试、提交、推送、发送票证给 QA。

但是,第二天 QA 退回了故障单,说 NPE 仍然存在,并且其中包含一个新的、几乎类似的堆栈跟踪

com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract com.redacted.SalesResponsePagination com.redacted.StatisticsService.findSalesData(com.redacted.ConfStats) throws com.redacted.AsyncException' threw an unexpected exception: java.lang.NullPointerException
    at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:389)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
    at ... (typical GWT + Tomcat stacktrace)
Caused by: java.lang.NullPointerException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
    at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
    at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source)
    at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
    at java.util.stream.ReferencePipeline.forEach(Unknown Source)
    at com.redacted.StatisticsControllerImpl.replacePrices(StatisticsControllerImpl.java:310)
    at com.redacted.StatisticsControllerImpl.findSalesData(StatisticsControllerImpl.java:288)
    at com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:561)
    ... 33 more
Caused by: java.lang.NullPointerException

此堆栈跟踪与上一个完全相同,除了两点:

  • 此调用使用的是NativeMethodAccessorImpl,而不是GeneratedMethodAccessor752。比较:
    at com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    at sun.reflect.GeneratedMethodAccessor752.invoke(Unknown Source)
    vs
    at com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

  • 这个缺少 lambda 的堆栈跟踪。 发生 NPE 的那一行丢失了。

这最初让我很反感。为什么堆栈跟踪的最后一部分丢失了?我要求 QA 重新测试并重新附加堆栈跟踪几次,直到我得到一个完整的;但是,我最后得到的完整的还是基于GeneratedMethodAccesor

现在,如果我理解正确,sun.reflect.NativeMethodAccessorImpl 用于方法的第一次调用,直到 JIT 有足够的信息以sun.reflect.GeneratedMethodAccessorNNN 的形式为该方法生成优化的访问器。
我不明白的是:如果Native 正在使用我的代码而Generated 正在使用JIT 生成的代码,Native 不应该显示关于我的代码的更多信息,而不是更少?

所以我的问题是:
为什么在sun.reflect.NativeMethodAccessorImpl 中抛出的 lambda 运行时异常似乎缺少 lambda 的堆栈跟踪?

这可能是 JDK 源代码中的错误吗?尤其是当sun.reflect.GeneratedMethodAccessor 中抛出的相同异常包含 lambda 堆栈跟踪时没有问题。


以下代码设法获得与第一个类似的堆栈跟踪。我可以通过分别使用足够低或足够高的第一个参数(即java test.Main 1java test.Main 30)来强制使用NativeMethodAccessorGeneratedMethodAccesor
但是,它的 lambda 部分始终存在,无论是使用 Native 还是 Generated

package test;

import java.lang.reflect.Method;
import java.util.stream.IntStream;

class MyOtherClass {
    public void methodWithLambda(boolean fail) {
        IntStream.range(0, 1000).parallel().forEach(k -> {
            if (fail && k % 500 == 0)
                throw new NullPointerException();
        });
    }
    public String methodProxy(boolean fail) {
        methodWithLambda(fail);
        return "OK";
    }
}

class MyClass {
    public String methodReflected(Boolean fail) {
        return new MyOtherClass().methodProxy(fail);
    }
}

class Main {
    public static void main(String[] args) throws Exception {
        Class<MyClass> clazz = MyClass.class;
        Object instance = clazz.newInstance();
        Method method = clazz.getMethod("methodReflected", Boolean.class);
        int reps = args.length >= 1 ? Integer.valueOf(args[0]) : 20; 
        for (; reps --> 0;) {
            // Several non-failing calls to force creation of GeneratedMethodAccesor
            System.out.println((String) method.invoke(instance, false));
        }
        // Failing call
        System.out.println((String) method.invoke(instance, true));
    }
}

使用NativeMethodAccesor时上述代码的堆栈跟踪:

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at test.Main.main(Main.java:36)
Caused by: java.lang.NullPointerException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
        at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
        at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
        at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source)
        at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(Unknown Source)
        at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
        at java.util.stream.IntPipeline.forEach(Unknown Source)
        at java.util.stream.IntPipeline$Head.forEach(Unknown Source)
        at test.MyOtherClass.methodWithLambda(Main.java:8)
        at test.MyOtherClass.methodProxy(Main.java:14)
        at test.MyClass.methodReflected(Main.java:21)
        ... 5 more
Caused by: java.lang.NullPointerException
        at test.MyOtherClass.lambda$methodWithLambda$0(Main.java:10)
        at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(Unknown Source)
        at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Unknown Source)
        at java.util.Spliterator$OfInt.forEachRemaining(Unknown Source)
        at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
        at java.util.stream.ForEachOps$ForEachTask.compute(Unknown Source)
        at java.util.concurrent.CountedCompleter.exec(Unknown Source)
        at java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
        at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(Unknown Source)
        at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source)
        at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
        at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)

编辑:明确一点:我不是在寻找修复此 NPE 的方法,也不是在寻找强制打印 lambda 堆栈跟踪的方法。我想知道的是发生上述情况的原因:不同的实现?一个错误?与forEach() 有什么关系?

【问题讨论】:

  • 顺便问一下,你确定 sr 在任何时候都不为空吗?流可能包含空值,在这种情况下 sr.getCurrency()、sr.getPrice() 等会遇到 NPE。如果您不需要解决它们,您可以简单地在流中较早地过滤掉它们。
  • @Assafs 是的,我确定sr 不为空,否则它会在forEach 之前的peek(sr -&gt; ...)filter(sr -&gt; ...) 中失败。但我的问题不是关于 NPE,而是关于堆栈跟踪因反射库使用的访问器类而异的原因。
  • 您可以使用 lambda 包装器并为您的异常处理代码提供堆栈跟踪语句,这些语句将打印异常的堆栈跟踪。
  • @RipudamanSingh 感谢您的意见。我想知道的是打印或不打印的原因取决于使用的反射访问器。
  • @walen 如果您在一些描述此问题的 repo 上提供一个基本示例,那就太好了!

标签: java lambda reflection stack-trace jit


【解决方案1】:

这可能是JDK-6678999 的问题,“空字符串比较后缺少堆栈跟踪”:

在将字符串与 null 进行比较并捕获异常并重复操作后,JVM 开始抛出“stackless” NullPointerException(它在 9000 次循环后发生,但这是可变的)

对问题的评价是

服务器编译器编译方法时,抛出异常中的堆栈跟踪 出于性能目的,可以省略该方法。

[...] 如果用户总是想要堆栈跟踪,请使用 VM 的 -XX:-OmitStackTraceInFastThrow 选项。

所以,-XX:-OmitStackTraceInFastThrow 选项可能会解决问题。

请注意,该错误报告是针对 Java 6 的,但由于它已作为“无法修复”而关闭,因此它可能仍然相关,尽管您必须将“服务器编译器”替换为“c2 编译器”现在解释一下。

NativeMethodAccessorImplGeneratedMethodAccessor…的使用与此问题无关,除非两者有共同的原因;更多的执行次数可能会触发优化。

【讨论】:

  • 感谢您的参与!我将尝试根据这些新信息调整测试代码,看看是否可以重现该行为。
  • 仍然无法重现。不过,您的回答是我所看到的唯一与这种行为的可能原因相似的事情,所以无论如何我都会给予全额奖励。
猜你喜欢
  • 2013-03-02
  • 2015-06-18
  • 1970-01-01
  • 1970-01-01
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
相关资源
最近更新 更多