Как исключение ловят и обрабатываются на низком уровне?
У меня есть этот код -
try {
doSomething();
} catch (Exception e) {
e.printStackTrace();
}
Как это будет реализовано компилятором. Где проверка исключения, фактически введенного в сгенерированный код сборки?
Обновление
Я знаю, как перевод кода выше на байт-код. Байт-код только переводит try-catch в соответствующие блоки try-handler. Меня интересует, как он будет переведен на сборку/и/или обрабатывается jvm.
Ответы
Ответ 1
Стоимость блока try-catch
Грубо говоря, блок try
не добавляет код проверки исключений в сборку результатов. Это, в основном, не-op, если не исключено исключение. Вся медленная работа выполняется с помощью кода исключения.
Когда try-catch
скомпилирован JIT, таблица исключений добавляется помимо кода. Он отображает диапазоны адресов, где может возникнуть обработанное исключение для адреса соответствующего обработчика исключений. Примечание: это не индексы байт-кода, а реальные адреса памяти.
Как исключение выбрано в HotSpot?
- Неявные исключения:
NullPointerException
и StackOverflowError
обнаруживаются внутри обработчика сигнала в ответ на ошибку сегментации.
-
ArrayIndexOutOfBoundsException
, ClassCastException
и т.д. проверяются явно. Соответствующие проверки встраиваются в скомпилированный код, где выполняется доступ к массиву.
-
OutOfMemoryError
, а все остальные исключения, отбрасываемые из собственного кода, проверяются явно, когда выполняется переход состояния потока (vm- > java или native- > java).
- Все пользовательские исключения, отбрасываемые байт-кодом
athrow
. В быстром пути (когда существует обработчик catch
в том же фрейме), JIT компилирует athrow
в простой прыжок. В противном случае происходит деоптимизация и обработка исключений выполняется во время выполнения VM.
Ну, "Как исключение попадает на уровень сборки?"
Ни в коем случае.
Я имею в виду, что исключения, как правило, не попадают на уровень сборки - все тяжелые вещи (стекирование, поиск обработчика, деоптимизация, разблокировка монитора и т.д.) Выполняются во время выполнения VM, то есть в коде C.
Ответ 2
Если я правильно понял ваш вопрос, следующий код
public class Example {
public static void main(String[] args) {
try {
otherMethod();
}
catch (Exception e) {}
try {
otherMethod();
someMethod();
}
catch (SQLException e) {}
catch (IOException e) {}
}
public static void someMethod() throws IOException {throw new IOException();}
public static void otherMethod() throws SQLException, IOException {}
}
выдает следующий код (вычитание из читаемой пользователем) байтового кода.
// main method
0: invokestatic #2 // Method otherMethod:()V
3: goto 7
6: astore_1
7: invokestatic #2 // Method otherMethod:()V
10: invokestatic #4 // Method someMethod:()V
13: goto 21
16: astore_1
17: goto 21
20: astore_1
21: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception
7 13 16 Class java/sql/SQLException
7 13 20 Class java/io/IOException
Вы заметите Exception table
. Эти конструкторы инструктируют виртуальную машину, что если исключение типа type
происходит между инструкцией от from
до to
, тогда она должна goto
инструкция (смещение) target
. Он также инструктирует его нажать ссылку Exception
в стеке, чтобы его значение можно было скопировать и привязать к параметру в блоке catch
.
У вас также есть эта часть, относящаяся к вышеприведенному выражению throw
.
// someMethod method
0: new #6 // class java/io/IOException
3: dup
4: invokespecial #7 // Method java/io/IOException."<init>":()V
7: athrow
Инструкция athrow
выполняет следующие действия
выдает ошибку или исключение (обратите внимание, что остальная часть стека очищается, оставляя только ссылку на Throwable)
JVM объясняет, что происходит
Объектref должен иметь ссылку на тип и должен ссылаться на объект это экземпляр класса Throwable или подкласса Throwable. Он выталкивается из стека операндов. Объектref затем бросается поиск текущего метода (§2.6) для первого обработчика исключений который соответствует классу objectref, заданному алгоритмом в §2.10.
Если найден обработчик исключений, который соответствует objectref, он содержит расположение кода, предназначенного для обработки этого исключения. ПК регистр reset в это место, стек операнда текущего кадр очищается, objectref отбрасывается обратно в стек операнда и выполнение продолжается.
Если в текущем фрейме не найдено соответствующего обработчика исключений, это кадр.. Если текущий кадр представляет собой вызов синхронизированный метод, монитор, введенный или повторно введенный при вызове метод завершается так, как если бы выполнялась команда monitorexit (§monitorexit). Наконец, кадр его invoker восстанавливается, если такой фрейм существует, и objectref будет сброшен. Если такой фрейм существует, текущий поток завершается.
Таким образом, кадры стека продолжают появляться до тех пор, пока не будет найден один, который может обрабатывать возникшее исключение.
Как это будет реализовано компилятором. Где проверить, действительно ли исключено исключение в генерируемый код сборки?
Компилятор генерирует байт-код выше. Проверка исключений не производится, а только инструкции байтового кода. athrow
даст указание виртуальной машине выполнить задачу того, что мы называем throwing exception, что приведет к появлению стека, поискам таблиц исключений в текущем фрейме стека и т.д.
Ответ 3
У меня нет четкого ответа для вас, но я дам вам шаги по сборке, и вы сможете проанализировать его на основе вашего прецедента.
- Убедитесь, что ваш метод, который вас интересует, скомпилирован для сборки
- Я обычно использую 2 для циклов, чтобы разрушить анализ escape-кода и убедиться, что мой код не отмечен NOP
- Убедитесь, что вы используете отладочную сборку JVM или создали дизассемблер HotSpot - инструкции для создания на Mac.
- Запустите вашу программу с помощью java -XX: + UnlockDiagnosticVMOptions -XX: + PrintAssembly
Также есть инструмент GUI для анализа и визуализации файла журнала JIT-компилятора, он называется JITWatch.
Вот класс, который я опрокинул, чтобы проверить это, возможно, несколько подробный, но получил myMethod и doSomething, чтобы скомпилировать его для сборки.
public class Question {
public static void main(String[] args) {
long result = 0;
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
result += myMethod();
}
}
System.out.println(result);
}
private static long myMethod() {
try {
return doSomething();
}
catch (Exception e) {
return 100;
}
}
private static long doSomething() {
if (System.currentTimeMillis() % 2 == 0)
return System.currentTimeMillis();
else
throw new RuntimeException();
}
}
Ответ 4
Я бы назвал вас
ответ на вопрос о переполнении стека.
Для программ, запущенных на этих машинах, java-байтовый код - это машинный язык. Нет "языка ассемблера".