Интервью по Java — своевременная компиляция (JIT)

Java

Когда мы пишем код, количество строк внутри метода, естественно, должно быть как можно меньше, чтобы логика была ясной и легко читаемой.На самом деле преимуществ гораздо больше.Благодаря своевременной компиляции, это может даже улучшить производительность во время выполнения. Давайте сделаем это сегодня. Давайте посмотрим, как это работает.

Введение

Когда инициализация JVM будет завершена, исполнительный механизм преобразует байт-код в машинный код во время процесса выполнения вызова класса, после чего он может быть выполнен в операционной системе. В процессе конвертации байт-кода в машинный код еще происходит компиляция в виртуальной машине, т.е.即时编译.

Первоначально байт-код в JVM компилируется интерпретатором (Interpreter).Когда виртуальная машина обнаружит, что метод или блок кода запускается очень часто, она идентифицирует эти коды как热点代码.

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

Классификация

В виртуальной машине HotSpot есть два встроенных JIT, а именноC1 编译器иC2 编译器, процесс компиляции двух компиляторов отличается.

Компилятор C1

Компилятор C1 — это простой и быстрый компилятор, основной упор делается на локальную оптимизацию, он подходит для программ с коротким временем выполнения или требованиями к производительности запуска, также известными какClient Compiler, например, приложения с графическим интерфейсом предъявляют определенные требования к скорости запуска интерфейса.

Компилятор C2

Компилятор C2 — это компилятор настройки производительности для долго работающих серверных приложений, подходящий для программ с длительным временем выполнения или требованиями к максимальной производительности, также известным какServer CompilerНапример, к долго работающему Java-приложению на сервере предъявляются определенные требования для стабильной работы.

Многоуровневая компиляция

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

Java 7 представляет многоуровневую компиляцию, которая сочетает в себе преимущества производительности при запуске C1 и преимущества максимальной производительности C2.Мы также можем передавать параметры-clientили-serverПринудительно указывает режим своевременной компиляции виртуальной машины.

Многоуровневая компиляция делит состояние выполнения JVM на пять уровней:

Уровень 0: интерпретация и выполнение программы, а также функция мониторинга производительности (профилирование) включена по умолчанию.Если она не включена, может быть запущена компиляция второго уровня;

Уровень 1: может называться компиляцией C1, которая компилирует байт-код в собственный код, выполняет простую и надежную оптимизацию и не включает профилирование;

Уровень 2: также известный как компиляция C1, профилирование включено, и выполняется только компиляция C1 с профилированием количества вызовов методов и количества циклических выполнений;

Уровень 3: также известный как компиляция C1, выполняет все компиляции C1 с профилированием;

Уровень 4: его можно назвать компиляцией C2, которая также компилирует байт-код в собственный код, но допускает некоторые оптимизации, которые требуют много времени для компиляции, и даже некоторые ненадежные агрессивные оптимизации, основанные на информации мониторинга производительности.

Для трех состояний C1 эффективность выполнения варьируется от высокой к низкой: уровень 1, уровень 2, уровень 3.

Как правило, C2 работает более чем на 30% эффективнее, чем C1.

В Java8 многоуровневая компиляция включена по умолчанию,-clientи-serverНастройка уже недействительна. Если вы хотите включить только C2, вы можете отключить многоуровневую компиляцию (-XX:-TieredCompilation), если вы хотите использовать только C1, вы можете использовать параметры при включении многоуровневой компиляции:-XX:TieredStopAtLevel=1.

ты можешь пройтиjava -versionКомандная строка может напрямую просматривать режим компиляции, используемый текущей системой:

C:\Users\Administrator>java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

mixed modeДелегат - это режим смешанного компиляции по умолчанию, в дополнение к этому режиму, мы также можем использовать-XintПараметр заставляет виртуальную машину работать в режиме компиляции только интерпретатора, где JIT вообще не мешает; вы также можете использовать параметр-XcompЗаставьте виртуальную машину работать в режиме JIT-компиляции. Например:

C:\Users\Administrator>java -Xint -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, interpreted mode)

C:\Users\Administrator>java -Xcomp -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, compiled mode)

Триггерные критерии

В виртуальной машине HotSpot热点探测является триггерным критерием для JIT.

Обнаружение горячих точек — это обнаружение горячих точек на основе счетчика. Виртуальная машина, использующая этот метод, устанавливает счетчик для каждого метода для подсчета времени выполнения метода. Если время выполнения превышает определенный порог, это считается «методом горячих точек».

Виртуальная машина подготавливает два типа счетчиков для каждого метода: счетчик вызовов метода (Invocation Counter) и задний счетчик (Back Edge Counter). При условии определения рабочих параметров виртуальной машины эти два счетчика имеют определенный порог, при превышении которого счетчик переполняется, инициируется JIT-компиляция.

счетчик вызовов методов

Счетчик вызовов метода используется для подсчета количества вызовов метода.Пороговое значение по умолчанию составляет 1500 раз в режиме C1 и 10000 раз в режиме C2.-XX: CompileThresholdустановить; а в случае многоуровневой компиляции-XX: CompileThresholdУказанное пороговое значение будет недействительным и будет динамически корректироваться в соответствии с текущим количеством компилируемых методов и количеством потоков компиляции. Компилятор JIT запускается, когда сумма счетчика метода и счетчика обратной связи превышает пороговое значение счетчика метода.

Счетчик задней кромки

Счетчик обратного фронта используется для подсчета количества раз, когда код тела цикла выполняется в методе. Инструкция, которая сталкивается с обратным переходом потока управления в байт-коде, называется «обратный фронт». Это значение используется для расчета, следует ли запускать Компиляция C1 Порог C1 по умолчанию равен 13995, а C2 по умолчанию равен 10700, если многоуровневая компиляция не включена. -XX: OnStackReplacePercentage=Nустановить; а в случае многоуровневой компиляции,-XX: OnStackReplacePercentageУказанный порог также будет недопустимым и будет динамически корректироваться в соответствии с текущим количеством компилируемых методов и количеством потоков компиляции.

Основная цель установки заднего счетчика — инициировать компиляцию OSR (On StackReplacement), то есть компиляцию в стеке. В некоторых сегментах кода с длинными циклами цикла, когда цикл достигает порога счетчика циклов, JVM будет рассматривать этот код как горячую точку, а JIT-компилятор компилирует этот код в машинный язык и кэширует его. код будет заменен напрямую, а кэшированный машинный язык будет выполнен.

Технология оптимизации

Компиляция JIT использует некоторые классические методы оптимизации компиляции для оптимизации кода, то есть с помощью некоторых рутинных проверок и оптимизаций она может разумно скомпилировать код с оптимальной производительностью во время выполнения. Существует два основных типа:方法内联,逃逸分析.

встраивание метода

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

Этот тип операции выполнения требует защиты сцены и запоминания адреса выполнения перед выполнением, восстановления сцены после выполнения и продолжения выполнения в соответствии с исходным сохраненным адресом. Следовательно, вызовы методов генерируют определенное количество времени и пространства (на самом деле это можно понимать как своего рода上下文切换облегченная версия).

Тогда для тех методов, чей основной код не очень велик и вызывается часто, это потребление времени и места будет очень большим.

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

JVM автоматически определяет методы горячих точек и оптимизирует их с помощью встраивания методов. мы можем пройти-XX:CompileThresholdустановить порог для метода горячих точек. Но следует подчеркнуть, что метод горячих точек не обязательно будет встроен JVM, если тело метода слишком велико, JVM не будет выполнять встроенные операции. Порог размера тела метода также можно оптимизировать, настроив параметр:

  1. Часто выполняемые методы, по умолчанию размер тела метода менее 325 байт будут встроены, мы можем передать-XX:MaxFreqInlineSize=Nустановить значение размера;
  2. Это не тот метод, который часто выполняется.По умолчанию размер метода составляет менее 35 байт для встраивания.Мы также можем передать-XX:MaxInlineSize=Nдля сброса значения размера.

Затем мы можем увидеть, как метод встроен, настроив параметры JVM:

// 在控制台打印编译过程信息
-XX:+PrintCompilation
// 解锁对 JVM 进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对 JVM 进行诊断
-XX:+UnlockDiagnosticVMOptions
// 将内联方法打印出来
-XX:+PrintInlining

Оптимизация методов горячих точек может эффективно повысить производительность системы.Как правило, мы можем улучшить встраивание методов следующими способами:

  1. Уменьшите порог активной области или увеличьте порог тела метода, задав параметры JVM, чтобы можно было встроить больше методов, но этот метод требует большего потребления памяти;
  2. В программировании избегайте написания большого количества кода в одном методе и привыкайте к использованию небольших тел методов;
  3. Старайтесь использовать ключевые слова final, private и static для изменения методов. Методы кодирования требуют дополнительной проверки типов из-за наследования.

Это связано с замечанием, сделанным в начале: чем меньше содержимого в методе, тем проще выполнить встраивание метода, когда метод часто выполняется, тем самым оптимизируя производительность.

анализ побега

Escape-анализ — это метод анализа, позволяющий определить, ссылается ли объект на внешний метод или к нему обращается внешний поток.Компилятор оптимизирует код в соответствии с результатом escape-анализа.

Это может быть установлено через аргументы JVM:

-XX:+DoEscapeAnalysis 开启逃逸分析(jdk1.8 默认开启)
-XX:-DoEscapeAnalysis 关闭逃逸分析

В основном существует три конкретных метода оптимизации:栈上分配,锁消除,标量替换.

выделение стека

По умолчанию создание объекта в Java выделяет память в куче, и когда объект в памяти кучи больше не используется, он должен быть переработан через механизм сборки мусора. Этот процесс относительно создания и уничтожения объектов, выделенных в стеке., который потребляет больше времени и производительности.

В это время, если анализ побега обнаруживает, что объект используется только в методе, он размещает объект в стеке.

Однако текущая реализация виртуальной машины HotSpot усложняет реализацию распределения стека.Можно сказать, что эта оптимизация пока не реализована в HotSpot, поэтому вы, возможно, не сможете испытать эту оптимизацию на данный момент. (данные, которые я прочитал, показывают, что это еще не реализовано в Java8). Реализация, если у вас есть какие-либо другие выводы, пожалуйста, оставьте сообщение).

снятие блокировки

Если он находится в однопоточной среде, нет необходимости использовать потокобезопасный контейнер вообще, но даже если он используется, поскольку не будет конкуренции потоков, в это время JIT-компиляция заблокирует блокировку метода этого объекта. Например:

    public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
        }

Его можно установить с помощью аргументов JVM:

-XX:+EliminateLocks 开启锁消除(jdk1.8 默认开启)
-XX:-EliminateLocks 关闭锁消除

скалярная замена

Escape-анализ доказывает, что к объекту не будет доступа извне.Если объект может быть разделен, объект может не быть создан, когда программа фактически выполняется, и вместо этого могут быть созданы его переменные-члены. После разделения объекта переменные-члены объекта могут быть размещены в стеке или регистре, и исходному объекту не нужно выделять место в памяти. Такая оптимизация компиляции называется скалярной заменой.

Например:

    public void foo() {
        TestInfo info = new TestInfo();
        info.id = 1;
        info.count = 99;
        // to do something
    }

После escape-анализа код оптимизируется для:

    public void foo() {
        id = 1;
        count = 99;
        // to do something
    }

Его можно установить с помощью аргументов JVM:

-XX:+EliminateAllocations 开启标量替换(jdk1.8 默认开启)
-XX:-EliminateAllocations 关闭就可以了

Суммировать

Сегодняшний контент, от самого простого здравого смысла方法内部行数和逻辑需要尽可能简单Представьте, понять процесс, оптимизирующий HIVM, оптимизирующий горячий код через счету со временной компиляцией. Если у вас есть какие-либо идеи, пожалуйста, оставьте сообщение ниже.

Если вам интересно, вы можете посетить мой блог или обратить внимание на мой официальный аккаунт и номер заголовка, возможно, будут неожиданные сюрпризы.

death00.github.io/