Пару дней назад я получил билет поддержки для этого 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 (выяснение причин, по которым некоторые продажи были зарегистрированы с нулевой ценой или валютой, появилось позже). Протестируйте, подтвердите, отправьте заявку в 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)
против
at com.redacted.StatisticsServiceImpl.findSalesData(StatisticsServiceImpl.java:83)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
В этом файле отсутствовала трассировка стека для лямбды. Линия, где произошло NPE, отсутствовала.
Это изначально отбросило меня. Почему пропала las часть стека? Я попросил QA повторно протестировать и повторно присоединить трассировку стека пару раз, пока я не получил полную; однако, полный, который я получил в конце, был основан на GeneratedMethodAccesor
еще раз .
Теперь, если я правильно понимаю, sun.reflect.NativeMethodAccessorImpl
используется для первых вызовов метода, пока у JIT не будет достаточно информации для генерации оптимизированного аксессора для этого метода в форме sun.reflect.GeneratedMethodAccessorNNN
.
Чего я не понимаю, так это: если Native
использует мой код, а Generated
использует сгенерированный код JIT, не должно ли Native
показывать больше информации о моем коде, не меньше?
Итак, мой вопрос:
Почему исключения из среды выполнения лямбды, добавленные внутрь sun.reflect.NativeMethodAccessorImpl
, по-видимому, пропускают трассировку стека лямбды?
Может ли это быть ошибкой в исходном коде JDK? Особенно, когда то же исключение, которое выдается внутри sun.reflect.GeneratedMethodAccessor
, включает в себя трассировку лямбда-стека без проблем.
Следующий код позволяет получить трассировку стека, аналогичную первой. Я могу принудительно использовать NativeMethodAccessor
или GeneratedMethodAccesor
, запустив его с достаточно низким или высоким первым параметром соответственно (т.е. java test.Main 1
или java test.Main 30
).
Однако его лямбда-часть присутствует всегда, будь то 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, ни способов заставить отпечаток стека лямбда-вывода печатать. То, что я хочу знать, является причиной, почему вышеупомянутое случается: различные реализации? Жук? Что-то делать с forEach()
?