Перевод статьи Tomasz Nurkiewicz
Я убежден, что чтение и понимание стектрейсов – важный навык, который должен быть у каждого программиста для того чтобы эффективно отыскивать источник проблемы на любом JVM-языке (см. также: «Фильтрация нерелевантных строчек стектрейса» (англ.) и «Логгирование корневой причны исключения в первую очередь» (англ.)). Так что давайте начнем с небольшого упражнения. Какие методы будут присутствовать в стектрейсе при вызове такого кода? foo(), bar() или оба?
В C# возможны оба варианта. Все зависит от того, как исключение перевыброшено в bar() — throw e вместо изначального стектрейса (начинающийся с foo()) подставляет место из которого оно было выброшено снова (bar()). С другой стороны, просто «throw» перевыбрасывает исключение, сохраняя изначальный стектрейс. Java использует второй подход (с синтаксисом первого) и даже не позволяет использование первого подхода напрямую. Но что насчет несколько измененной версии:
foo() только создает исключение, но не выбрасывает, а возвращает его. Это исключение затем выбрасывается из совершенно другого метода. Как теперь выглядит стектрейс? Удивительно, но он все еще указывает на foo() как будто исключение было выброшено оттуда, точно так же как и в первом примере:
Возникает вопрос: что проиходит? Получается, стектрейс создается не тогда, когда исключение выброшено, а в момент создания исключения. В подавляющем большинстве случаев эти события происходят в одном и том же месте, так что никто не парится. Многе начинающие джависты даже не подозревают, что можно создать объект исключения и назначить его переменной или полю, или даже передать его куда-нибудь.
Но откуда же на самом деле берется стектрейс? Ответ достаточно прост – из метода Throwable.fillInStackTrace()!
Обратите внимание, метод не final, и это позволяет его немножко поломать. Можно не только обойти создание стектрейса и выбросить исключение без какого-либо контекста, но даже полностью переопределить стектрейс!
Если выбросить такое исключение, JVM напечатает такое (серьезно, попробуйте сами!):
Именно так. ExceptionFromHell еще интереснее. Оно не содержит стектрейса, так что доступны только имя класса и сообщение. И ни JVM, ни какая-либо библиотека логгирования не могут с этим ничего поделать. Кому вообще могло бы понадобиться так делать (я не имею в виду SponsoredException)?
Удивительно, но некоторые считают построение стектрейса дорогостоящей операцией. Ведь это нативный метод который должен пройти по всему стеку чтобы создать StackTraceElement’ы. Однажды мне встретилась библиотека, использовавшая подобную технику для ускорения выброса исключений. Я написал небольшой бенчмарк, чтобы увидеть разницу в производительности между выбросом обычного RuntimeException, исключения без стектрейса и обычным методом возвращающим значение. Тесты запускаются с разной глубиной стека с помощью рекурсии.
Вот что получилось:

Очевидно, что чем длинее стектрейс, тем больше времени занимает выброс исключения. Также очевидно, что при вменяемой длине стектрейса эта процедура не должна занимать более 100 μs (быстрее чем чтение 1 MiB главной памяти). Наконец, выброс исключения без стектрейса в 2-5 раз быстрее. Но честно говоря, если это вас беспокоит, ваша проблема где-то в другом месте. Если ваше приложение выбрасывает исключения настолько часто, что вам приходится это оптимизировать, что-то явно не так с вашим дизайном. Не пытайтесь починить Джаву, она не сломана.
Выводы:
- стектрейс всегда указывает на место, где исключение было создано, а не туда, откуда оно было выброшено — хотя в 99% случаев это одно и то же место.
- вы полностью контролируете стектрейс возвращаемый вашими исключениями
- создание стектрейса имеет свою цену, но если это становится узким местом вашего приложения, что-то явно не так.
Оригинал статьи: http://java.dzone.com/articles/where-do-stack-traces-come
Переведено с помощью: http://translatedby.com/