Трассировка лямбда-стека отсутствует при использовании NativeMethodAccessor вместо GeneratedMethodAccessor

Вопрос задан: 1 год назад Последняя активность: 1 год назад
up 12 down

Пару дней назад я получил билет поддержки для этого 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()?

1 ответ

Возможно, для Вашего проекта будут необходимы бесплатные векторные карты. На нашем сайте представлены карты для всех стран.

Реклама

up 3 down

Это может быть проблемой JDK-6678999, «Stacktrace отсутствует после сравнения нулевых строк»:

После сравнения строки со значением NULL и перехвата исключения и повторения операции JVM начинает выдавать исключение NullPointerException без стека (это происходит после 9000 циклов, но это переменная)

Оценка вопроса

Когда компилятор сервера компилирует метод, трассировка стека в исключении   этим способом может быть опущен для целей производительности.

[…] Если пользователю всегда нужны трассировки стека, используйте параметр -XX: -OmitStackTraceInFastThrow для виртуальной машины.

Итак, вариант -XX:-OmitStackTraceInFastThrow может решить проблему.

Обратите внимание, что отчет об ошибке был против Java 6, но, поскольку он был закрыт как «Не исправлять», он все еще может быть актуальным, хотя вам придется заменить «компилятор сервера» на «компилятор c2» в пояснении.

Использование NativeMethodAccessorImpl или же GeneratedMethodAccessor… не имеет отношения к этому вопросу, за исключением того, что оба имеют общую причину; большее количество выполнений может вызвать оптимизацию.

Ошибка 505

Что-то пошло не так

Попробуйте воспользоваться поиском