【问题标题】:NullPointerException in native java code while performing parallelStream.forEach(..)执行 parallelStream.forEach(..) 时本机 Java 代码中的 NullPointerException
【发布时间】:2016-08-17 11:07:03
【问题描述】:

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

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 本机代码中引发的。 我的意思是,NullPointerException 不是从我自己的代码中抛出的——不是从我的消费者函数中抛出的,它作为 forEach(..) 方法中的参数出现。

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

这是orgsToCaluclate的初始化:

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

有什么想法吗?..

【问题讨论】:

  • 您是否尝试过检查集合是否为空、null 或其他?此外,如果您没有至少约 6000 个条目,请避免使用 parallelStreams。
  • 是的,该集合有 11 个条目,没有空值。为什么建议不要在小情况下使用parallelStream?我确实怀疑这可能与parallelStream有关,但无法准确指出原因。
  • 它不会因(普通,串行)流而失败吗?
  • 它并不总是下降。即,它不会落在我的机器上,它会落在 QA 机器上,很少,并非总是如此。因此,通过将其移至常规(串行)流,它不会发生,但我仍然无法识别并行流中的问题,
  • 如果串行流从来没有发生过,这告诉我你的代码中的某些东西不是线程安全的。这意味着它不会在单线程情况下失败 - 只有在多个线程中运行时。您应该检查您的代码并确保您使用的数据结构都是线程安全的,或者受同步或锁保护。

标签: java nullpointerexception jvm java-8 java-stream


【解决方案1】:

我们习惯说异常的堆栈跟踪反映了“它发生的位置”,但这是一个不准确的说法。异常的堆栈跟踪通常会反映其实例的创建位置

当我们有表单的代码时,

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 outForkJoinTask 将通过反射创建一个已经遇到的异常的新实例,当线程不匹配时我们可以看到in its source code

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

但是当我们仔细观察source code 时,我们会发现整个反射创作都被一个

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

块。因此,如果操作确实失败,则不会出现异常。相反,代码已经失败以返回原始异常。这表明反射代码没有失败,而是我们看到由getThrowableException() 返回的成功、反射创建的NullPointerException 实例,后来被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 保持未报告,而是直接抛出来自另一个线程的原始异常。

【讨论】:

  • 非常感谢,真的很有帮助。
【解决方案2】:

查看堆栈跟踪,您的代码中似乎发生了异常,它正在尝试获取该异常的新实例,但由于 NullPointerException 而未能这样做,您能否检查您的代码是否有任何代码在 foreach 中调用的会引发任何异常并确保 这些异常的所有构造函数都是正确的

    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]

【讨论】:

  • @DimaLih 不,tonakai 在这里是正确的。查看stacktrace(或者ForkJoinTask的源码)
  • 也许您可以提供更多代码,我们可以看看,或者您的某些代码不是@RealSkeptic 提到的线程安全
  • @DimmaLih 是的,您的 forEach() 消费者中存在异常。该异常不必与您的 Set 中的元素或您的 Set 的修改相关。您没有看到该异常,因为 a) 您没有捕获并记录它,b) 您的 Consumer 代码在 ForkJoinTask 中运行。当 ForkJoinTask 确定它遇到异常时,它会尝试报告该异常。为了提供准确的堆栈跟踪,如果当前线程没有抛出异常,则将构造一个相同类型的新异常。 NPE 失败的是新异常的构造。
  • @DimmaLih Throwable wx = (Throwable)(noArgCtor.newInstance()) 这是 ForkJoinTask.getThrowableException 中的第 598 行失败。 noArgCtor 是一个非空的 java.lang.reflect.Constructor,它表示由您的 forEach() 使用者代码引发的异常的公共、无参数构造函数。以某种方式实例化此异常的新实例失败并出现 NPE - 这就是您在堆栈跟踪中看到的内容。
  • @Stefan Zobel:我们确实看到了主要的异常,至少是它的类型。这是NullPointerException。关键是这个答案是正确的,因为它对异常进行了反思,但它误解了结果。娱乐成功。见my answer
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-13
  • 2020-08-22
  • 1970-01-01
相关资源
最近更新 更多