Как работает стек вызовов функций при обработке исключений в Java
Что проверяют
Понимаете ли вы, что происходит между throw и catch: как JVM ищет обработчик и почему по пути снимаются кадры стека. Следом обычно спрашивают, когда выполняется finally, что будет при неперехваченном исключении и почему исключения дорогие.
Как отвечать
У каждого потока свой стек, каждый вызов метода кладёт кадр с локальными переменными и стеком операндов. У каждого метода в байткоде есть таблица исключений: диапазон инструкций, адрес обработчика и тип, который он ловит.
- поиск обработчика: после
throw(инструкцияathrow) или неявной ошибки вродеNullPointerExceptionJVM смотрит таблицу текущего метода. Запись подходит, если инструкция в диапазоне, а исключение является экземпляром указанного типа или наследника; - раскрутка стека: если записи нет, кадр снимается, и поиск повторяется в вызывающем методе. Локальные переменные снятых кадров пропадают;
finally: компилятор копирует его код в каждый путь выхода и добавляет обработчик на любой тип, поэтомуfinallyотрабатывает и при раскрутке.try-with-resourcesзакрывает ресурсы и прикрепляет ошибки закрытия черезaddSuppressed();- неперехваченное исключение: если стек потока исчерпан, вызывается
UncaughtExceptionHandler, по умолчанию он печатает стек-трейс. Умирает этот поток, а JVM живёт, пока есть другие не-daemon потоки.
Про стоимость: стек-трейс снимается в конструкторе Throwable через fillInStackTrace(), а не в момент throw. Сбор трейса и есть главная цена, поэтому исключения не годятся для обычной логики ветвления. Для дешёвых исключений есть конструктор с writableStackTrace = false.
Где спотыкаются
Утверждают, что неперехваченное исключение всегда завершает программу. Это верно, только когда падает последний не-daemon поток, а исключение в задаче пула вообще может молча осесть в Future.
Пишут return в finally. Он проглатывает летящее исключение, и ошибка исчезает без следа.
Удивляются NullPointerException без стек-трейса в логах прода. HotSpot после многих повторов одного неявного исключения переходит на заранее созданный объект без трейса, отключается это флагом -XX:-OmitStackTraceInFastThrow.