Подтвердить что ты не робот

Исключение NullPointerException, в которое он не может быть выброшен

Я получаю исключение NullPointerException в куске кода, который не может его выбросить. Я начинаю думать, что нашел ошибку в JRE. Я использую javac 1.8.0_51 в качестве компилятора, и проблема возникает как в jre 1.8.0_45, так и в последней версии 1.8.0_60.

Линия, бросающая исключение, находится внутри цикла, который находится внутри функции лямбда-замыкания. Мы запускаем такое замыкание в искре 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 никогда не является нулевым, так как у вас есть "новые" несколько строк выше
  • largeIndex является примитивным
  • элемент никогда не является нулевым, поскольку он уже используется несколько строк выше
  • getFoo() возвращает double без распаковки
  • фактор примитивен

Я не могу запускать один и тот же вход локально и отлаживать его: он запускается на искровом кластере. Поэтому я добавил несколько debug 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 [largeIndex - 1] в данный момент бросает нулевой указатель. Обратите внимание, что также следующие броски:

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

Но не следующее:

foo[1] += ....;

Я посмотрел байт-код в файле класса и не нашел ничего странного. Вы правильно имеете ссылку на foo и largeIndex в стеке до iconst_1, isub и daload.

Я просто отправляю это, чтобы собирать идеи, прежде чем думать об ошибке jre. Кто-нибудь из вас испытал тот же самый класс проблем с использованием искры? или лямбда-функции в целом. Возможно ли запустить jvm с флагом отладки, чтобы помочь мне понять это странное поведение? Или я должен где-то передать вопрос кому-то?

4b9b3361

Ответ 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

который может быть слишком резким.