NullPointerException 被扔进了无法抛出的地方
2022-09-04 23:25:33
我在一段无法抛出的代码中得到了一个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永远不会为空,因为你上面有几行“新”
- 较大索引是原始的
- item 永远不会为 null,因为它在上面的几行中已经使用过
- 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 和 largerIndex。
我只是发布这个是为了在思考jre错误之前收集想法。你们中是否有人使用火花遇到过同一类问题?或一般的 lambda 函数。是否可以使用一些调试标志运行jvm来帮助我理解这种奇怪的行为?或者我应该将问题提交给某个地方的人吗?