Рубрики
Без рубрики

Метод встраивания в JVM

Узнайте о методе, встроенном в JVM, и о том, как компилятор Just-In-Time достигает этого.

Автор оригинала: baeldung.

1. введение

В этом уроке мы рассмотрим, какой метод встроен в виртуальную машину Java и как он работает.

Мы также увидим, как получить и прочитать информацию, связанную с встраиванием из JVM, и что мы можем сделать с этой информацией для оптимизации нашего кода.

2. Что Такое Метод Встраивания?

По сути, inlining-это способ оптимизации скомпилированного исходного кода во время выполнения путем замены вызовов наиболее часто выполняемых методов его телами.

Несмотря на то, что существует компиляция, она выполняется не традиционным компилятором javac , а самой JVM. Если быть более точным, это ответственность компилятора Just-In-Time (JIT) , который является частью JVM; javac создает только байт-код и позволяет JIT выполнять магию и оптимизировать исходный код.

Одним из наиболее важных последствий такого подхода является то, что если мы скомпилируем код с использованием старой Java, то же самое . class файл будет быстрее на новых JVM. Таким образом, нам не нужно перекомпилировать исходный код, а только обновлять Java.

3. Как Это Делает JIT?

По сути, компилятор JIT пытается встроить методы, которые мы часто вызываем, чтобы избежать накладных расходов на вызов метода . При принятии решения о том, следует ли встроить метод или нет, учитываются две вещи.

Во-первых, он использует счетчики, чтобы отслеживать, сколько раз мы вызываем метод. Когда метод вызывается более определенного количества раз, он становится “горячим”. По умолчанию этот порог установлен на 10 000, но мы можем настроить его с помощью флага JVM во время запуска Java. Мы определенно не хотим встроить все, так как это отнимет много времени и создаст огромный байт-код.

Мы должны иметь в виду, что встраивание будет происходить только тогда, когда мы достигнем стабильного состояния. Это означает, что нам нужно будет повторить выполнение несколько раз, чтобы предоставить достаточную информацию о профилировании для JIT-компилятора.

Кроме того, “горячее” не гарантирует, что метод будет встроен. Если он слишком большой, ОН не будет встроен в него. Допустимый размер ограничен флагом -XX:FreqInlineSize= , который указывает максимальное количество инструкций байт-кода для встроенного метода.

Тем не менее, настоятельно рекомендуется не изменять значение этого флага по умолчанию, если мы не абсолютно уверены в том, какое влияние он может оказать. Значение по умолчанию зависит от платформы – для 64-разрядного Linux оно равно 325.

JIT inlines static , private , или final методы в целом . И хотя методы public также являются кандидатами на встраивание, не каждый открытый метод обязательно будет встроен. JVM должна определить, что существует только одна реализация такого метода . Любой дополнительный подкласс предотвратит встраивание, и производительность неизбежно снизится.

4. Поиск Горячих Методов

Мы, конечно, не хотим догадываться, что делает ДЖИТ. Поэтому нам нужен какой-то способ увидеть, какие методы встроены или нет в Интернете. Мы можем легко достичь этого и записать всю эту информацию в стандартный вывод, установив некоторые дополнительные флаги JVM во время запуска:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

Первый флаг будет регистрироваться при компиляции JIT. Второй флаг включает дополнительные флаги , включая -XX:+PrintInlining , которые будут печатать, какие методы будут встроены и где.

Это покажет нам онлайн-методы в виде дерева. Листья снабжены аннотациями и помечены одним из следующих вариантов:

  • inline (hot) – этот метод помечен как горячий и встроен
  • слишком большой – метод не горячий, но и его сгенерированный байт-код слишком большой, поэтому он не встроен
  • горячий метод слишком большой – это горячий метод, но он не встроен, так как байт-код слишком большой

Мы должны обратить внимание на третье значение и попытаться оптимизировать методы с меткой “горячий метод слишком большой”.

Как правило, если мы находим горячий метод с очень сложным условным оператором, мы должны попытаться отделить содержимое оператора if- и увеличить степень детализации, чтобы JIT мог оптимизировать код. То же самое касается операторов switch и for- loop.

Мы можем сделать вывод, что встраивание ручного метода-это то, что нам не нужно делать для оптимизации нашего кода. JVM делает это более эффективно, и мы, возможно, сделаем код длинным и трудным для выполнения.

4.1. Пример

Давайте теперь посмотрим, как мы можем проверить это на практике. Сначала мы создадим простой класс, который вычисляет сумму первых N последовательных положительных целых чисел:

public class ConsecutiveNumbersSum {

    private long totalSum;
    private int totalNumbers;

    public ConsecutiveNumbersSum(int totalNumbers) {
        this.totalNumbers = totalNumbers;
    }

    public long getTotalSum() {
        totalSum = 0;
        for (int i = 0; i < totalNumbers; i++) {
            totalSum += i;
        }
        return totalSum;
    }
}

Затем простой метод будет использовать класс для выполнения вычисления:

private static long calculateSum(int n) {
    return new ConsecutiveNumbersSum(n).getTotalSum();
}

Наконец, мы вызовем метод различное количество раз и посмотрим, что произойдет:

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) {
    calculateSum(i);
}

В первом запуске мы собираемся запустить его 1000 раз (меньше порогового значения в 10 000, упомянутого выше). Если мы будем искать вывод для метода calculateSum () , мы его не найдем. Это ожидаемо, так как мы не называли его достаточно часто.

Если теперь мы изменим количество итераций на 15 000 и снова проведем поиск в выходных данных, мы увидим:

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   inline (hot)

Мы видим, что на этот раз метод выполняет условия для встраивания, и JVM встраивает его.

Стоит еще раз упомянуть, что если метод слишком большой, JIT не будет встроен в него, независимо от количества итераций. Мы можем проверить это, добавив еще один флаг при запуске приложения:

-XX:FreqInlineSize=10

Как мы можем видеть в предыдущем выводе, размер нашего метода составляет 12 байт. Флаг -XX: FreqInlineSize ограничит размер метода, допустимый для встраивания, до 10 байт. Следовательно, в этот раз встраивание не должно происходить. И действительно, мы можем подтвердить это, еще раз взглянув на результат:

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   hot method too big

Хотя мы изменили значение флага здесь для иллюстрации, мы должны подчеркнуть рекомендацию не изменять значение по умолчанию флага -XX:FreqInlineSize без крайней необходимости.

5. Заключение

В этой статье мы рассмотрели, какой метод встроен в JVM и как это делает JIT. Мы описали, как мы можем проверить, подходят ли наши методы для встраивания или нет, и предложили, как использовать эту информацию, пытаясь уменьшить размер часто вызываемых длинных методов, которые слишком велики для встраивания.

Наконец, мы проиллюстрировали, как мы можем идентифицировать горячий метод на практике.

Все фрагменты кода, упомянутые в статье, можно найти в нашем репозитории GitHub .