java - 执行 parallelStream.forEach(..) 时 native java 代码中的 NullPointerException

标签 java nullpointerexception jvm java-8 java-stream

我有以下异常(堆栈跟踪):

java.lang.NullPointerException
at sun.reflect.GeneratedConstructorAccessor171.newInstance(Unknown Source) ~[?:?]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:1.8.0_40]
at java.lang.reflect.Constructor.newInstance(Constructor.java:422) ~[?:1.8.0_40]
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598) ~[?:1.8.0_40]
at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677) ~[?:1.8.0_40]
at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735) ~[?:1.8.0_40]
at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160) ~[?:1.8.0_40]
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174) ~[?:1.8.0_40]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) ~[?:1.8.0_40]
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) ~[?:1.8.0_40]
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:583) ~[?:1.8.0_40]
at com.tradair.tnet.services.trades.TradeService.updateUnrealizedPNL(TradeService.java:173) ~[tnet.jar:5.1.1.0-SNAPSHOT]

从我的 TradeService 类开始:

    public void updateUnrealizedPNL(Set<Org> orgsToCaluclate, Set<Org> orgsToSendUpdate) {
    orgsToCaluclate.parallelStream().forEach(o -> {
        pnlService.updateMidPrices(o);
        Collection<SystemTradeOrder> allLiveTradesByOrgId = tradesRepository.getAllLiveTradesByOrgId(o.getId());
        updateUnrealizedPNL(o, allLiveTradesByOrgId);
    });

    // more code ....

所以看起来异常是在运行 forEach(..) 方法时在 java native 代码中抛出的。 我的意思是,NullPointerException 不是从我自己的代码中抛出的 - 不是从作为参数出现在 forEach(..) 方法中的我的消费者函数中抛出的。

我仔细检查了这段代码运行时没有对 orgsToCaluclate 集进行修改。

这是 orgsToCaluclate 的初始化:

        Set<Org> orgsToCaluclate = getMarginOrgs();
        orgsToCaluclate = orgsToCaluclate.stream()
                .filter(org -> !isOrgInCloseout(org.getId())).collect(Collectors.toSet());

有什么想法吗?..

最佳答案

我们习惯说异常的堆栈跟踪反射(reflect)了“它发生的地方”,但这是一个不精确的说法。异常的堆栈跟踪通常反射(reflect)了它的实例是在哪里创建的。

当我们有表单的代码时,

1   String s=null;
2   s.length();

当我们尝试取消引用 null 以调用方法 length() 时,JRE 将创建一个 NullPointerException 实例,因此它的堆栈跟踪将报告第 2 行。

但是,当我们有如下代码时

1   String s=null;
2   if(s == null) {
3       RuntimeException rt=new NullPointerException();
4       throw rt;
5   }

堆栈跟踪不会报告在何处检测到错误条件(第 2 行)或在何处抛出异常(第 4 行),但会报告实例在何处发生已在 3 行中创建。

对于大多数实际情况,这些地方足够接近,不会产生显着差异,但在这里,我们有一个特殊的情况。

作为tonakai has pointed out , ForkJoinTask 将通过反射创建一个已经遇到的异常的新实例,如我们所见in its source code当线程不匹配时。

当它成功时,它的堆栈跟踪将准确地反射(reflect)新异常实例的创建位置,这是在一些执行反射实例创建的生成代码中。当然,这种成功的创建与JRE在执行相同代码时由于错误条件而创建异常的情况无法区分。

但是当我们仔细观察 source code ,我们看到整个反射创作都包含在

584             try {
…
604             } catch (Exception ignore) {
605             }

阻止。所以如果操作真的失败了,看不出有什么异常。相反,代码已经失败以返回原始异常。这表明反射代码没有失败,而是我们看到成功地反射创建的 NullPointerException 实例由 getThrowableException() 返回,后来被 故意抛出ForkJoinTask 报告在处理过程中另一个线程中有一个 NullPointerException

但是这段代码将新异常的原因初始化为指向原始异常。例如。以下代码:

import java.util.stream.IntStream;

public class Main
{
    public static void main(String[] args) {
        Thread main=Thread.currentThread();
        IntStream.range(0, 1000).parallel().forEach(i -> {
            if(Thread.currentThread()!=main)
                throw new NullPointerException();
        });
    }
}

打印

Exception in thread "main" java.lang.NullPointerException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
    at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:189)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
    at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:560)
    at Main.main(Main.java:7)
Caused by: java.lang.NullPointerException
    at Main.lambda$main$0(Main.java:9)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

所以你仍然能够认出发生了什么。您只需要注意原因。由于您问题中的堆栈跟踪看起来不像典型的 Throwable.printStackTrace()输出,它可能是生成此输出的代码忽略了异常的原因属性。


作为附录,我们可以使用自定义异常类型检查如果重新创建真的失败会发生什么:

import java.util.stream.IntStream;

public class Main
{
    public static class CustomException extends RuntimeException {
        public CustomException() {
            System.err.println("will deliberately fail");
            throw new NullPointerException();
        }
        private CustomException(String message) {
            super(message);
        }
    }
    public static void main(String[] args) {
        Thread main=Thread.currentThread();
        IntStream.range(0, 1000).parallel().forEach(i -> {
            if(Thread.currentThread()!=main)
                throw new CustomException("forced failure");
        });
    }
}

将打印

will deliberately fail
Exception in thread "main" Main$CustomException: forced failure
    at Main.lambda$main$0(Main.java:18)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

表明在反射重建期间通过默认构造函数抛出的 NullPointerException 未报告,而是直接抛出来自其他线程的原始异常。

关于java - 执行 parallelStream.forEach(..) 时 native java 代码中的 NullPointerException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38994907/

相关文章:

java - Proguard with scala 和 android-plugin : java. lang.StringIndexOutOfBoundsException:字符串索引超出范围:160

java - Bluej - JButton 图像加载

java - 具有非均匀对象数组的 JTable,自定义 TableCellRenderer 实现的返回

java - 当 EditText 为空时防止 nullPointerException

java - 从不同 JVM 中的 Java 桌面应用程序中执行 Java main 方法

java - GCLocker 中的慢路径和快路径是什么?

Java/SpotBugs,如果在接口(interface)中声明 "named static inner class",那么它是什么?

java - 使用 java 从 WSDL 创建 Web 服务客户端

java - 输入的完美数

java - 为什么IO的Thread甜蜜点是20倍? [以前是: Which ExecutionContext to use in playframework?]