java - 使用 NativeMethodAccessor 而不是 GeneratedMethodAccessor 时缺少 Lambda 堆栈跟踪

标签 java lambda reflection stack-trace jit

几天前,我收到了这个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。比较:
    在 com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    在 sun.reflect.GeneratedMethodAccessor752.invoke(未知来源)
    对比
    在 com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
    在 sun.reflect.NativeMethodAccessorImpl.invoke0( native 方法)

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

一开始这让我很反感。为什么缺少堆栈跟踪的最后一部分?我要求 QA 重新测试并重新附加堆栈跟踪几次,直到我得到一个完整的;然而,我最后得到的完整的是再次基于GeneratedMethodAccessor

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

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

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


以下代码设法获得与第一个代码类似的堆栈跟踪。我可以强制使用 NativeMethodAccessorGeneratedMethodAccessor 通过分别使用足够低或足够高的第一个参数运行它(即 java test.Main 1java test.Main 30)。
但是,它的 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));
    }
}

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

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() 有关吗?

最佳答案

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

After comparing a string to null and catching the exception and repeating the operation, JVM starts throwing "stackless" NullPointerException (it occurs after 9000 loops but this is variable)

问题的评价是

When the server compiler compiles a method, the stack trace in an exception thrown by that method may be omitted for performance purposes.

[…] If the user always wants stack traces, use the -XX:-OmitStackTraceInFastThrow option to the VM.

因此,选项 -XX:-OmitStackTraceInFastThrow 可能会解决这个问题。

请注意,错误报告是针对 Java 6 的,但由于它已被关闭为“不会修复”,它可能仍然是相关的,尽管您必须在现在解释。

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

关于java - 使用 NativeMethodAccessor 而不是 GeneratedMethodAccessor 时缺少 Lambda 堆栈跟踪,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54196907/

相关文章:

java - 在雅虎日历中创建 Activity

java - 如何防止 Excel 单元格格式发生变化

java - 从 double 转换为 int 并不总是只删除小数部分

java - Python Java forEach 等价物

python - 如何编写 lambda 函数来计算 Python sort() 中字符串中的出现次数

C# 注册嵌入式 Directshow 过滤器

Java 记录数组解决方案

amazon-web-services - AWS-SQS批处理大小和Lambda方法

generics - Kotlin 从泛型类型中获取属性

java - 获取 Scala 案例类的类型标记的 java 类