【问题标题】:NullPointerException thrown in where it can't be thrown在不能抛出的地方抛出 NullPointerException
【发布时间】:2015-12-30 12:43:48
【问题描述】:

我在一段不能抛出它的代码中得到一个 NullPointerException。 我开始考虑在 JRE 中发现了一个错误。我使用 javac 1.8.0_51 作为编译器,问题出现在 jre 1.8.0_45 和最新的 1.8.0_60 中。

抛出异常的行在一个循环内,它在一个闭包 lambda 函数内。我们在 spark 1.4 中运行这样的关闭。 该行执行了 1-2 百万次,我得到的错误不是确定性的,使用相同的输入,每 3 或 4 次运行一次。

我在这里粘贴相关的代码:

        JavaRDD .... mapValues(iterable -> {
                LocalDate[] dates = ...
                long[] dateDifferences = ...

                final double[] fooArray = new double[dates.length];
                final double[] barArray = new double[dates.length];
                for (Item item : iterable) {
                    final LocalDate myTime = item.getMyTime();
                    final int largerIndex = ...
                    if (largerIndex == 0) {
                        ...
                    } else if (largerIndex >= dates.length - 1) {
                        ...
                    } else {
                        final LocalDate largerDate = dates[largerIndex];
                        final long daysBetween = ...
                        if (daysBetween == 0) {
                            ...
                        } else {
                            double factor = ...
                            // * * * NULL POINTER IN NEXT LINE * * * //
                            fooArray[largerIndex - 1] += item.getFoo() * factor;
                            fooArray[largerIndex] += item.getFoo() * (1 - factor);
                            barArray[largerIndex - 1] += item.getBar() * factor;
                            barArray[largerIndex] += item.getBar() * (1 - factor);
                        }
                    }
                }
                return new NewItem(fooArray, barArray);
            })
            ...

我开始分析代码,发现:

  • fooArray 永远不会为空,因为上面有“新”几行
  • largerIndex 是原始的
  • item 永远不会为空,因为它已经在上面几行中使用过
  • getFoo() 返回双精度,不拆箱
  • 因子是原始的

我无法在本地运行相同的输入并对其进行调试:这是在 spark 集群上运行的。所以我在抛线前加了一些调试 println:

System.out.println("largerIndex: " + largerIndex);
System.out.println("foo: " + Arrays.toString(foo));
System.out.println("foo[1]: " + foo[1]);
System.out.println("largerIndex-1: " + (largerIndex-1));
System.out.println("foo[largerIndex]: " + foo[largerIndex]);
System.out.println("foo[largerIndex - 1]: " + foo[largerIndex - 1]);

这是输出:

largerIndex: 2
foo: [0.0, 0.0, 0.0, 0.0, ...]
foo[1]: 0.0
largerIndex-1: 1
foo[largerIndex]: 0.0
15/10/01 12:36:11 WARN scheduler.TaskSetManager: Lost task 0.0 in stage 7.0 (TID 17162, host13): java.lang.NullPointerException
    at my.class.lambda$mymethod$87560622$1(MyFile.java:150)
    at my.other.class.$$Lambda$306/764841389.call(Unknown Source)
    at org.apache.spark.api.java.JavaPairRDD$$anonfun$toScalaFunction$1.apply(JavaPairRDD.scala:1027)
    ...

所以 foo[largerIndex - 1] 当前正在抛出空指针。请注意,以下内容也会引发它:

int idx = largerIndex - 1;
foo[idx] += ...;

但不是以下:

foo[1] += ....;

我查看了类文件中的字节码,并没有发现什么奇怪的地方。您在 iconst_1、isub 和 daload 之前的堆栈中正确地引用了 foo 和 largeIndex。

我只是发布这个来收集想法,然后再考虑一个 jre 错误。 有没有人在使用 spark 时遇到过同样的问题?或一般的 lambda 函数。是否可以使用一些调试标志运行 jvm 来帮助我理解这种奇怪的行为?还是我应该将问题提交给某个地方的某个人?

【问题讨论】:

  • getFoo()body 是否可能引发 NPE?请告诉我们。可能是堆栈跟踪省略了一些内联代码,或类似的东西。
  • 另外请将for (Item item : iterable)替换为for (final Item item : iterable)
  • 鉴于您可以合理可靠地引发错误,但不是确定性的,您是否可以尝试逐渐减少涉及的代码量,以尝试查明问题所在?如果我们能够达到我们也可以复制它的阶段,那么帮助您会更容易。你能在不同平台下尝试相同的代码吗?
  • 是否有可能在这段代码附近进行了一些字节码重写......所以实际运行的代码与源代码显示的有很大不同?
  • 这段代码在解释模式下会在JVM上抛出NPE吗?

标签: java lambda nullpointerexception apache-spark


【解决方案1】:

在我看来,这与此处描述的问题非常相似(JIT 问题): http://kingsfleet.blogspot.com.br/2014/11/but-thats-impossible-or-finding-out.html

您的观察,它并非每次都发生,并且在阅读代码时“不可能”发生与那里描述的完全相同。 要找出它,请使用命令行选项将您的方法排除在 JIT 之外(您需要指定正确的类/方法名称):

-XX:CompileCommand=exclude,java/lang/String.indexOf

或者通过使用完全关闭它

-Xint

这可能太激烈了。

【讨论】:

  • 感谢所有试图提供帮助的人,但不幸的是,我无法再执行建议的更改。我们继续编写代码,错误自动消失。我接受这个答案,因为它可能会解决问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-12
  • 1970-01-01
  • 2017-08-11
  • 2013-09-07
  • 2013-06-18
  • 2016-07-07
相关资源
最近更新 更多