NullPointerException в Java без использования StackTrace

333

У меня были экземпляры нашего Java-кода NullPointerException, но когда я пытаюсь зарегистрировать StackTrace (который в основном вызывает вызов Throwable.printStackTrace()), все, что я получаю, это:

java.lang.NullPointerException

Кто-нибудь еще сталкивался с этим? Я пробовал поискать в Google «трассировку пустого стека в пустом указателе Java», но ничего подобного не встречал.

Эдвард Штерн
источник
Каков контекст? Есть несколько вовлеченных потоков? У меня были проблемы при попытке получить трассировку стека исключений в SwingWorker.
Майкл Майерс
Здесь не задействованы потоки, просто старая Java.
Эдвард Штерн,
1
@ Божо - нет - пока не знаю, как воспроизвести NullPointer.
Эдвард Штерн,
1
связанные: stackoverflow.com/questions/1076191/…
Джошуа Голдберг
Больше информации -XX:-OmitStackTraceInFastThrowв дупе: stackoverflow.com/questions/4659151/…
Вадим

Ответы:

407

Вероятно, вы используете JSM HotSpot (первоначально Sun Microsystems, позже купленную Oracle, часть OpenJDK), которая выполняет большую оптимизацию. Чтобы вернуть трассировку стека, вам нужно передать эту опцию -XX:-OmitStackTraceInFastThrowв JVM.

Оптимизация заключается в том, что, когда исключение (обычно NullPointerException) возникает впервые, печатается полная трассировка стека, и JVM запоминает трассировку стека (или, возможно, только местоположение кода). Когда это исключение возникает достаточно часто, трассировка стека больше не печатается, чтобы повысить производительность и не заполнять журнал идентичными трассами стека.

Чтобы увидеть, как это реализовано в JVM HotSpot, возьмите его копию и найдите глобальную переменную OmitStackTraceInFastThrow. В прошлый раз, когда я посмотрел код (в 2019 году), он был в файле graphKit.cpp .

Роланд Иллиг
источник
1
Спасибо за чаевые. Любая идея, если есть какие-то скрытые ошибки для передачи этой опции (это кажется довольно безобидным, если мое приложение не вызывает кучу исключений)?
Эдвард Штерн
Там нет скрытых ошибок, о которых я знаю. Когда вы посмотрите на исходный код Hotspot, вы увидите, что эта опция используется только в одном месте (graphKit.cpp). И это выглядит хорошо для меня.
Роланд Иллиг
34
Думала добавить дополнительный бит информации, когда трассировки стека получает оптимизированный прочь, это потому , что он получил полностью обработаны по крайней мере один раз: jawspeak.com/2010/05/26/...
шаракан
1
Я использую OpenJDK JVM версии 1.8.0u171 (Debian 9), и, похоже, он также принимает этот -XX:-OmitStackTraceInFastThrowфлаг. Я еще не подтвердил, если по этой причине я также не смог напечатать трассировки стека (например, используя e.printStackTrace), но это кажется весьма вероятным. Я расширил ответ, чтобы отразить это открытие.
Крис У.
В нашем случае первые 125 исключений имели трассировку стека, а затем остальные 3 ротации файлов журналов не имели ни одного. Этот ответ был очень полезен в поиске виновника.
Сухмел
61

Как вы упомянули в комментарии, вы используете log4j. Я обнаружил (случайно) место, где я написал

LOG.error(exc);

вместо типичного

LOG.error("Some informative message", e);

из-за лени или, возможно, просто не думая об этом. К сожалению, это не ведет себя так, как вы ожидаете. API логгера фактически принимает Object в качестве первого аргумента, а не строку, а затем вызывает toString () для аргумента. Таким образом, вместо того, чтобы получить красивую трассировку стека, он просто распечатывает toString - что в случае с NPE довольно бесполезно.

Возможно, это то, что вы испытываете?

Стивен Шланскер
источник
+1: это объяснило бы описанное поведение, и вы не единственный, кто обнаружил это :)
Питер Лэнг
4
На самом деле у нас есть стандартная политика никогда не использовать первую форму выше (LOG.error (exc);) - мы всегда используем сигнатуру с 2 параметрами, чтобы добавить в журналы некоторый описательный оператор вместо простой трассировки стека.
Эдвард Штерн,
5
Конечно, но политика не означает, что она всегда выполняется правильно! Похоже, стоило упомянуть, по крайней мере.
Стивен Шланскер
Правда, но в данном случае это было ;-)
Эдвард Штерн
28

Мы видели такое же поведение в прошлом. Оказалось, что по какой-то безумной причине, если исключение NullPointerException происходило в одном и том же месте в коде несколько раз, через некоторое время использование Log.error(String, Throwable)перестало бы включать полные трассировки стека.

Попробуйте заглянуть дальше в свой журнал. Вы можете найти виновника.

РЕДАКТИРОВАТЬ: эта ошибка звучит актуально, но она была исправлена ​​так давно, что, вероятно, не причина.

Мэтт Солнит
источник
2
Ошибка закрыта, но флаг -XX: -OmitStackTraceInFastThrow все еще необходим для обхода оптимизации производительности.
Джошуа Голдберг
Я видел это в последнее время много. Любые подсказки относительно того, что может быть причиной, или как это исправить? Система ведения журнала, возможно, работала в течение нескольких дней, и фактическая причина была устранена, не говоря уже об утомительном поиске ...
Павел Веселов
5
Павел, ты пробовал -XX:-OmitStackTraceInFastThrowфлаг JVM, предложенный Джошуа? См. Также stackoverflow.com/a/2070568/6198 .
Мэтт Solnit
1
Это было для нас. Спасибо.
Эндрю Чонг
20

Вот объяснение: Hotspot вызвал исключения, чтобы потерять их следы стека в производстве - и исправить

Я проверил это на Mac OS X

  • Java-версия "1.6.0_26"
  • Java (TM) SE Runtime Environment (сборка 1.6.0_26-b03-383-11A511)
  • Java HotSpot (TM) 64-разрядная серверная виртуальная машина (сборка 20.1-b02-383, смешанный режим)

    Object string = "abcd";
    int i = 0;
    while (i < 12289) {
        i++;
        try {
            Integer a = (Integer) string;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Для этого конкретного фрагмента кода 12288 итераций (+ частота?), По-видимому, являются пределом, в котором JVM решила использовать предварительно выделенное исключение ...

Бенуа Геро
источник
10

exception.toString не дает вам StackTrace, он только возвращает

краткое описание этого броска. Результатом является объединение:

* the name of the class of this object
* ": " (a colon and a space)
* the result of invoking this object's getLocalizedMessage() method

exception.printStackTraceВместо этого используйте для вывода StackTrace.

Питер Лэнг
источник
Извините, я ошибся в своем оригинальном сообщении. Я регистрирую их через Log4J, который использует printStackTrace ().
Эдвард Штерн,
1
Вы пытались использовать, getStackTrace()чтобы убедиться, что проблема не с вашим регистратором?
Питер Лэнг
1
Если вы используете log4j, обязательно отправьте исключение как часть аргумента методу log. Я отправлю ответ с этим.
Рави Валлау
@raviaw действительная точка! @ Эдвард Штерн: можете ли вы подтвердить, что вы определенно используете 2-аргументную форму метода log4j? Я знаю, что вы упомянули в ответе ниже, что это политика компании, но вы АБСОЛЮТНО уверены, что в этом случае вы следуете политике?
KarstenF
Это может быть длинный выстрел, но возможно ли, что исключение происходит из какого-то стороннего кода? Может быть, это (плохо написанная) оболочка исключения, метод которой toString () просто возвращает имя класса упакованной исключительной ситуации и который не может обеспечить базовую трассировку стека. Попробуйте вставить что-то вроде logger.info ("Exception class =" + exc.class.getCanonicalName ()) в ваш блок catch и посмотрите, что вы получите.
KarstenF
4

Альтернативное предложение - если вы используете Eclipse, вы можете установить точку останова на самом NullPointerException (в перспективе Отладка перейдите на вкладку «Точки останова» и нажмите на маленький значок с!)

Проверьте оба параметра: «пойман» и «не пойман» - теперь, когда вы запускаете NPE, вы сразу же получаете точку останова, а затем можете пройти и посмотреть, как именно она обрабатывается и почему вы не получаете трассировку стека.

Стивен Шланскер
источник
1

toString()возвращает только имя исключения и необязательное сообщение. Я бы предложил позвонить

exception.printStackTrace()

чтобы выбросить сообщение, или если вам нужны подробности:

 StackTraceElement[] trace = exception.getStackTrace()
Шелдон Янг
источник
Смотрите выше - я неправильно сказал - я использую printStackTrace ().
Эдвард Штерн,
1

(Ваш вопрос до сих пор неясен, звонит ли ваш код printStackTrace() или это делает обработчик журналирования.)

Вот несколько возможных объяснений того, что может происходить:

  • Используемый регистратор / обработчик был настроен на вывод только строки сообщения об исключении, а не полной трассировки стека.

  • Ваше приложение (или некоторая сторонняя библиотека) регистрирует исключение, используя, LOG.error(ex);а не 2-аргументную форму (например) метода Log4j Logger.

  • Сообщение приходит откуда-то иное, чем вы думаете; например, это на самом деле какой-то сторонний библиотечный метод или какой-то случайный материал, оставшийся от предыдущих попыток отладки.

  • Записываемое исключение перегружает некоторые методы, чтобы скрыть трассировку стека. В этом случае исключение не будет подлинным исключением NullPointerException, но будет некоторым пользовательским подтипом NPE или даже каким-то неподключенным исключением.

Я думаю, что последнее возможное объяснение довольно маловероятно, но люди, по крайней мере, обдумывают, что делать, чтобы «предотвратить» обратный инжиниринг. Конечно, только честным разработчикам удается только усложнить жизнь.

Стивен С
источник
1

Когда вы используете AspectJ в своем проекте, может случиться, что какой-то аспект скрывает свою часть трассировки стека. Например, сегодня у меня было:

java.lang.NullPointerException:
  at com.company.product.MyTest.test(MyTest.java:37)

Эта трассировка стека была напечатана при запуске теста через верный Maven.

С другой стороны, при запуске теста в IntelliJ была напечатана другая трассировка стека:

java.lang.NullPointerException
  at com.company.product.library.ArgumentChecker.nonNull(ArgumentChecker.java:67)
  at ...
  at com.company.product.aspects.CheckArgumentsAspect.wrap(CheckArgumentsAspect.java:82)
  at ...
  at com.company.product.MyTest.test(MyTest.java:37)
Роланд Иллиг
источник
0

Это выведет исключение, используйте только для отладки, вы должны лучше обрабатывать ваши исключения.

import java.io.PrintWriter;
import java.io.StringWriter;
    public static String getStackTrace(Throwable t)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }
Майкл Д. Иризарри
источник