Java 使用NativeMethodAccessor而不是GeneratedMethodAccessor时缺少Lambda堆栈跟踪
几天前,我收到了此Java 使用NativeMethodAccessor而不是GeneratedMethodAccessor时缺少Lambda堆栈跟踪,java,lambda,reflection,stack-trace,jit,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.Async
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)
});
此堆栈跟踪与前一个堆栈跟踪完全相同,除了两件事:
- 此调用使用的是
而不是NativeMethodAccessorImpl
。比较:GeneratedMethodAccessor752
位于com.redact.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
在sun.reflect.GeneratedMethodAccessor752.invoke(未知源)
vs
位于com.redact.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
在sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)
- 这一个缺少lambda的堆栈跟踪发生NPE的行丢失。
生成的方法或再次
现在,如果我理解正确的话,用于方法的第一次调用,直到JIT有足够的信息为该方法生成一个优化的accesor,形式为sun.reflect.generatedMethodAccessornn
我不明白的是:如果Native
正在使用我的代码,而Generated
正在使用JIT生成的代码,那么Native
不应该显示更多关于我的代码的信息,而不是更少吗
所以我的问题是:
为什么在sun.reflect.NativeMethodAccessorImpl中抛出的lambda运行时异常似乎缺少lambda的堆栈跟踪?
这可能是JDK源代码中的错误吗?特别是当sun.reflect.GeneratedMethodAccessor
中抛出的异常包含lambda堆栈跟踪而没有问题时
下面的代码设法获得与第一个类似的堆栈跟踪。我可以强制使用NativeMethodAccessor
或GeneratedMethodAccesor
,方法是分别使用足够低或足够高的第一个参数运行它(即java test.Main 1
或java test.Main 30
)。
但是,它的lambda部分始终存在,无论是使用本机
还是生成的
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));
}
}
编辑:我只是想说清楚:我不是在寻找修复此NPE的方法,也不是在寻找强制打印lambda堆栈跟踪的方法。我想知道的是发生上述情况的原因:不同的实现?虫子?与forEach()
有关?这可能是“空字符串比较后Stacktrace丢失”的问题:
将字符串与null进行比较并捕获异常并重复该操作后,JVM开始抛出“stackless”NullPointerException(它发生在9000个循环之后,但这是可变的)
对这一问题的评价是正确的
当服务器编译器编译方法时,抛出异常中的堆栈跟踪
通过该方法,出于性能目的,可以省略
[…]如果用户总是想要堆栈跟踪,请对VM使用-XX:-OmitStackTraceInFastThrow选项
因此,选项-XX:-ommitStackTraceInFastThrow
可以解决这个问题
请注意,错误报告是针对Java 6的,但由于它已被关闭为“不会修复”,因此它可能仍然相关,尽管您必须在现在的解释中将“服务器编译器”替换为“c2编译器”
使用NativeMethodAccessorMPL
或GeneratedMethodAccessor…
与此问题无关,除非两者都有共同的原因;更高的执行次数可能会触发优化。顺便问一下,您确定sr在任何时候都不为空吗?流可能包含空值,在这种情况下,sr.getCurrency()、sr.getPrice()等将遇到NPE。如果您不需要解决这些问题,您只需在流的前面过滤掉它们。@Assafs是的,我确信sr
不为空,否则在forEach
之前的peek(sr->…)
或filter(sr->…)
中失败。但我的问题不是关于NPE,而是关于堆栈跟踪因反射库使用的accesor类不同而不同的原因。您可以使用lambda包装器并使用stacktrace语句提供异常处理代码,这些语句将打印异常的堆栈跟踪。@RipudamanSingh感谢您的输入。我想知道的是打印或不打印的原因,这取决于使用的反射存取器。@walen如果您提供一个描述此问题的某个回购协议的基本示例,那就太好了!谢谢你叫我进来!我会试着用这些新的信息来调整测试代码,看看我是否能重现这种行为,但仍然无法重现。你的回答是我所见过的唯一一件与这种行为的可能原因相去甚远的事情,所以无论如何,我将给予全部的赏金。
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));
}
}
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)