我在一段无法抛出 NullPointerException 的代码中得到一个 NullPointerException 。
我开始认为在 JRE 中发现了一个错误。我使用javac 1.8.0_51作为编译器,问题出现在jre 1.8.0_45和最新的1.8.0_60中。
抛出异常的行位于一个循环内,该循环位于闭包 lambda 函数内。我们在 Spark 1.4 中运行这样的闭包。
该行执行了 1-200 万次,并且每 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 永远不会为 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]);
这是输出:
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)
...
So foo[largerIndex - 1]当前正在抛出空指针。请注意,以下代码也会抛出它:
int idx = largerIndex - 1;
foo[idx] += ...;
但不是以下内容:
foo[1] += ....;
我查看了类文件中的字节码,没有发现任何奇怪的地方。在 icont_1、isub 和 daload 之前,您在堆栈中正确地引用了 foo 和largerIndex。
我发布此文章只是为了在考虑 jre bug 之前收集想法。
你们中有人在使用 Spark 时遇到过类似的问题吗?或一般的 lambda 函数。是否可以使用一些调试标志来运行 jvm 来帮助我理解这种奇怪的行为?或者我应该将问题提交给某个地方的某人?