NullPointerException в Java без использования StackTrace
У меня были экземпляры нашего кода Java поймать NullPointerException
, но когда я пытаюсь войти в StackTrace (который в основном вызывает Throwable.printStackTrace()
) все, что я получаю, это:
java.lang.NullPointerException
Кто-нибудь еще сталкивался с этим? Я пробовал поискать в Google "трассировку пустого стека Java-указателя с нулевым указателем", но ничего подобного не встречал.
12 ответов
Вы, вероятно, используете JSM HotSpot (первоначально Sun Microsystems, позже купленную Oracle, часть OpenJDK), которая выполняет большую оптимизацию. Чтобы вернуть трассировку стека, вам нужно передать опцию -XX:-OmitStackTraceInFastThrow
в JVM.
Оптимизация заключается в том, что, когда исключение (обычно NullPointerException) возникает впервые, выводится полная трассировка стека, и JVM запоминает трассировку стека (или, возможно, только местоположение кода). Когда это исключение возникает достаточно часто, трассировка стека больше не печатается, чтобы повысить производительность и не заполнять журнал идентичными трассами стека.
Чтобы увидеть, как это реализовано в JVM HotSpot, возьмите его копию и найдите глобальную переменную OmitStackTraceInFastThrow
, В прошлый раз, когда я посмотрел код (в 2019 году), он был в файле graphKit.cpp.
Как вы упомянули в комментарии, вы используете log4j. Я обнаружил (случайно) место, где я написал
LOG.error(exc);
вместо типичного
LOG.error("Some informative message", e);
из-за лени или, возможно, просто не думая об этом. К сожалению, это не ведет себя так, как вы ожидаете. API логгера фактически принимает Object в качестве первого аргумента, а не строку, а затем вызывает toString() для аргумента. Таким образом, вместо того, чтобы получить красивую трассировку стека, он просто распечатывает toString - что в случае с NPE довольно бесполезно.
Возможно, это то, что вы испытываете?
Мы видели такое же поведение в прошлом. Оказалось, что по какой-то сумасшедшей причине, если NullPointerException возникало в одном и том же месте кода несколько раз, через некоторое время Log.error(String, Throwable)
остановится, включая полные следы стека.
Попробуйте заглянуть дальше в свой журнал. Вы можете найти виновника.
РЕДАКТИРОВАТЬ: эта ошибка звучит актуально, но она была исправлена так давно, что, вероятно, не причина.
Вот объяснение: 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 решила использовать предварительно выделенное исключение...
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.
Альтернативное предложение - если вы используете Eclipse, вы можете установить точку останова на самом NullPointerException (в перспективе отладки перейдите на вкладку "Точки останова" и нажмите на маленький значок с!)
Проверьте оба варианта: "пойман" и "не пойман" - теперь, когда вы запускаете NPE, вы сразу же получаете точку останова, а затем можете пройти и посмотреть, как именно она обрабатывается и почему вы не получаете трассировку стека.
toString()
возвращает только имя исключения и необязательное сообщение. Я бы предложил позвонить
exception.printStackTrace()
чтобы выбросить сообщение, или если вам нужны подробности:
StackTraceElement[] trace = exception.getStackTrace()
VM перестает выводить трассировку стека исключений, которые были сгенерированы несколько раз. Это одна из оптимизаций, которая происходит при компиляции C2.
Согласно исходному коду OpenJDK, эта оптимизация применяется к следующим исключениям:
-NullPointerException-ArithmeticException-ArrayIndexOutOfBoundsException-ArrayStoreException-ClassCastException
(Ваш вопрос до сих пор неясен, звонит ли ваш код printStackTrace()
или это делает обработчик регистрации.)
Вот несколько возможных объяснений того, что может происходить:
Используемый регистратор / обработчик был настроен на вывод только строки сообщения об исключении, а не полной трассировки стека.
Ваше приложение (или некоторая сторонняя библиотека) регистрирует исключение, используя
LOG.error(ex);
вместо формы с двумя аргументами (например) метода log4j Logger.Сообщение приходит откуда-то иное, чем вы думаете; например, это на самом деле какой-то сторонний библиотечный метод или какой-то случайный материал, оставшийся от предыдущих попыток отладки.
Записываемое исключение перегружает некоторые методы, чтобы скрыть трассировку стека. В этом случае исключение не будет подлинным NullPointerException, но будет некоторым пользовательским подтипом NPE или даже каким-то неподключенным исключением.
Я думаю, что последнее возможное объяснение довольно маловероятно, но люди, по крайней мере, обдумывают, что делать, чтобы "предотвратить" обратный инжиниринг. Конечно, это только действительно делает жизнь честным разработчикам трудной.
Когда вы используете 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)
Это выведет исключение, используйте только для отладки, вы должны обрабатывать ваши исключения лучше.
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();
}
Я решил эту проблему, напечатав stackTrace внутри цикла for.
for (StackTraceElement st : e.getStackTrace()) {
LOGGER.error(st.toString());
}
При этом он печатает весь stackTrace. Другими методами я не могу получить больше информации.