wal*_*len 12 java reflection lambda jit stack-trace
几天前,我得到了一张支持票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)
Run Code Online (Sandbox Code Playgroud)
现在,这很简单,因为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)
});
Run Code Online (Sandbox Code Playgroud)
并替换.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
Run Code Online (Sandbox Code Playgroud)
除了两件事之外,这个堆栈跟踪与前一个完全相同:
此调用使用的是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发生的线路不见了.
这最初让我失望了.为什么堆栈跟踪的las部分缺失?我让QA重新测试并重新连接堆栈跟踪几次,直到我得到一个完整的; 然而,我最终得到的完整的一个GeneratedMethodAccesor再次基于.
现在,如果我理解正确,sun.reflect.NativeMethodAccessorImpl将用于方法的第一次调用,直到JIT有足够的信息以形式生成该方法的优化访问者sun.reflect.GeneratedMethodAccessorNNN.
我不明白的是:如果Native使用我的代码并Generated使用JIT生成的代码,不应该Native显示有关我的代码的更多信息,而不是更少?
所以我的问题是:
为什么抛出lambda运行时异常sun.reflect.NativeMethodAccessorImpl似乎缺少lambda的堆栈跟踪?
这可能是JDK源代码中的错误吗?特别是当内部抛出的同一异常sun.reflect.GeneratedMethodAccessor包括lambda堆栈跟踪没有问题时.
以下代码设法获得类似于第一个的堆栈跟踪.我可以分别强制使用NativeMethodAccessor或GeneratedMethodAccesor运行足够低或高的第一参数(即java test.Main 1或java 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));
}
}
Run Code Online (Sandbox Code Playgroud)
使用时堆栈跟踪以上代码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)
Run Code Online (Sandbox Code Playgroud)
编辑:只是要清楚:我不是在寻找修复这个NPE的方法,也没有办法强制lambda的堆栈跟踪打印.我想知道的是上面发生的原因:不同的实现?一个bug?与此有关forEach()?
这可能是JDK-6678999的问题,“空字符串比较后缺少 Stacktrace ”:
在将字符串与 null 进行比较并捕获异常并重复操作后,JVM 开始抛出“无堆栈”NullPointerException(它发生在 9000 次循环之后,但这是可变的)
问题的评价是
当服务器编译器编译一个方法时,出于性能目的,该方法抛出的异常中的堆栈跟踪可能会被省略。
[...] 如果用户总是想要堆栈跟踪,请对 VM 使用 -XX:-OmitStackTraceInFastThrow 选项。
因此,该选项-XX:-OmitStackTraceInFastThrow可能会解决问题。
请注意,错误报告是针对 Java 6 的,但由于它已被关闭为“不会修复”,因此它可能仍然相关,尽管您现在必须在解释中将“服务器编译器”替换为“c2 编译器”。
使用NativeMethodAccessorImpl或GeneratedMethodAccessor…与此问题无关,除非两者有共同的原因;更多的执行次数可能会触发优化。