Путаница с HotSpot JVM JIT
Например, цикл с 10000 раз в методе. Когда он выполняется 1000 раз, backedge_counter запускает JIT
сборник. И переводчик продолжает исполнять. Когда он зацикливается 4000 раз, JIT
компиляция завершена.
У меня вопрос: как остальные 6000 раз выполнить интерпретатором или выполнить собственный код? Или собственный код не выполняется, пока этот метод не будет вызван в следующий раз? И что происходит, когда этот метод вызывается в следующий раз?
2 ответа
Предполагая, что вы спрашиваете о HotSpot JVM, ответ заключается в том, что оставшиеся взаимодействия будут выполняться в скомпилированном коде.
HotSpot JVM имеет метод, известный как "замена в стеке", для переключения с интерпретатора на скомпилированный код во время работы метода.
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
замена в стеке
Также известный как "OSR". Процесс преобразования интерпретированного (или менее оптимизированного) стекового фрейма в скомпилированный (или более оптимизированный) стековый фрейм. Это происходит, когда интерпретатор обнаруживает, что метод находится в цикле, запрашивает компилятор создать специальный метод n с точкой входа где-то в цикле (в частности, в обратной ветви) и передает управление этому методу. Грубый обратный деоптимизации.
Если вы запускаете JVM с -XX:+PrintCompilation
флаг, компиляции OSR будут отмечены %
знак:
274 27 3 java.lang.String::lastIndexOf (52 bytes)
275 29 3 java.lang.String::startsWith (72 bytes)
275 28 3 java.lang.String::startsWith (7 bytes)
275 30 3 java.util.Arrays::copyOf (19 bytes)
276 32 4 java.lang.AbstractStringBuilder::append (29 bytes)
276 31 s 3 java.lang.StringBuffer::append (13 bytes)
283 33 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
^ ^
OSR bytecode index of OSR entry
ОБНОВИТЬ
Обычно после компиляции OSR регулярная компиляция также ставится в очередь, так что при следующем вызове метода он начнет работать непосредственно в режиме компиляции.
187 32 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
187 33 3 LoopTest::myLongLoop (43 bytes)
Однако, если обычная компиляция не будет завершена к моменту повторного вызова метода, метод запустится в интерпретаторе, а затем переключится на запись OSR внутри цикла.
Давайте повторим вопрос:
Может ли компилятор Java HotSpot изменить метод с интерпретируемого на скомпилированный в середине его выполнения?
Я думаю, что это возможно.
Задача не простая для движка (я накопил некоторый опыт в этой области, когда работал над компилятором Ahead-of-Time JUMP для карманных компьютеров PalmOS несколько лет назад). Когда двигатель решает переключиться, он должен учитывать, по крайней мере, следующие моменты:
Где находится счетчик программ? В интерпретируемом коде он находится на некотором смещении байт-кода от начала метода, точно зная, какой байт-код следует выполнить следующим. В оптимизированном собственном коде, как правило, байт-коды JVM не преобразуются в изолированные блоки машинных инструкций, а зависят друг от друга, переупорядочиваются не по порядку и так далее. Таким образом, при переключении может отсутствовать собственный адрес инструкции, который в точности соответствует программному счетчику байт-кода.
Где данные? Интерпретатор (вероятно) хранит все в стеке, оптимизированный нативный код использует смесь распределения регистров и стеков, которая будет отличаться для разных мест в нативном переводе.
Итак, я прочитал технический документ HotSpot. Он не дает четкого ответа на вопрос, но есть подсказка под "деоптимизацией". Когда новый класс загружается динамически или даже заменяется в сеансе отладки, предыдущие оптимизации, такие как встраивание, могут стать недействительными.
Поэтому виртуальная машина Java HotSpot должна быть способна динамически деоптимизировать (а затем, если необходимо, повторно оптимизировать) ранее оптимизированные горячие точки, даже при выполнении кода для горячей точки.
Это также переключение между скомпилированным и интерпретированным кодом, только наоборот. Поскольку это задокументированное поведение механизма HotSpot, я прихожу к выводу, что переход от интерпретированного к скомпилированному коду в рамках текущего вызова метода возможен.
РЕДАКТИРОВАТЬ:
Я не был достаточно ясно о том, что я понял, как суть вопроса.
Я понял, что есть метод, который делает цикл с 10000 итерациями следующим образом:
void loop() {
for (int i=0; i<10000; i++) {
// example loop body
objects[i].doSomething();
}
}
Например, после 4000 итераций компилятор HotSpot оптимизировал этот метод. Что происходит потом?
Есть два аспекта, один тривиальный и один сложный:
Тривиальным является то, что вызовы, которые происходят внутри цикла (например,
doSomething()
) вызовет их скомпилированную версию, как только она станет доступна. Я не упомянул об этом в своем первоначальном ответе, поскольку воспринимал это как должное.Сложный аспект: будет ли запущенный в данный момент
loop()
переключение выполнения с интерпретируемого на скомпилированный код при i=4000? Это то, что я понял как вопрос ОП.