Путаница с 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? Это то, что я понял как вопрос ОП.

Другие вопросы по тегам