抛出NullPointerException,无法抛出它

Jac*_*ack 13 java lambda nullpointerexception apache-spark

我在一段不能抛出它的代码中得到一个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);
            })
            ...
Run Code Online (Sandbox Code Playgroud)

我开始分析代码并发现:

  • fooArray永远不会为null,因为你上面有几行"new"
  • largeIndex是原始的
  • item永远不会为null,因为它已经在上面使用了几行
  • getFoo()返回double而没有取消装箱
  • 因素是原始的

我无法在本地运行相同的输入并对其进行调试:这是在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]);
Run Code Online (Sandbox Code Playgroud)

这是输出:

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)
    ...
Run Code Online (Sandbox Code Playgroud)

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

int idx = largerIndex - 1;
foo[idx] += ...;
Run Code Online (Sandbox Code Playgroud)

但不是以下内容:

foo[1] += ....;
Run Code Online (Sandbox Code Playgroud)

我看了一下类文件中的字节码,发现没什么奇怪的.您在iconst_1,isub和daload之前正确地在堆栈中引用了foo和greaterIndex.

我只想发布这个来收集想法,然后再考虑一个jre bug.有没有人使用spark经历过同类问题?或一般的lambda函数.是否可以运行带有一些调试标志的jvm来帮助我理解这种奇怪的行为?或者我应该将问题提交给某个人?

小智 2

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

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

-XX:CompileCommand=exclude,java/lang/String.indexOf
Run Code Online (Sandbox Code Playgroud)

或者通过使用完全关闭它

-Xint
Run Code Online (Sandbox Code Playgroud)

这可能太激烈了。