Как разогреть JVM

1. Обзор

JVM является одной из старейших и мощных виртуальных машин из когда-либо созданных.

В этой статье мы кратко рассмотрим, что значит подогревать JVM и как это сделать.

2. Основы архитектуры JVM

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

Этот процесс происходит в три этапа:

, Загрузка класса начальной загрузки: Bootstrap Class Loader ”загружает Java

код и необходимые классы Java, такие как java.lang.Object в память.

Эти загруженные классы находятся в JRE \ lib \ rt.jar .

, Загрузка класса расширения :

ExtClassLoader отвечает за загрузку всех файлов JAR, расположенных по пути java.ext.dirs . В приложениях не Maven или не Gradle, где разработчик добавляет JAR-файлы вручную, все эти классы загружаются на этом этапе.

, Загрузка класса приложения :

AppClassLoader загружает все классы, расположенные в пути к классам приложения.

Этот процесс инициализации основан на ленивой схеме загрузки.

3. Что прогревает JVM

После завершения загрузки классов все важные классы (используемые во время запуска процесса) помещаются в https://www.ibm.com/support/knowledgecenter/en/SSAW57 8.5.5/com.ibm.websphere.nd. .doc/ae/rdyn tunediskcache.html[JVM-кеш (собственный код)]- что делает их доступными быстрее во время выполнения. Другие классы загружаются по запросу.

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

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

  • Этот процесс настройки JVM известен как разогрев. **

4. Многоуровневая подборка

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

Мы можем использовать это свойство для принудительной загрузки критических методов в кеш при запуске приложения. Для этого нам нужно установить аргумент виртуальной машины с именем Tiered Compilation :

-XX:CompileThreshold -XX:TieredCompilation

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

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

Приложения, работающие на JBoss и JDK версии 7 с включенным этим аргументом VM, имеют тенденцию к аварийному завершению через некоторое время из-за задокументированного bug . Эта проблема была исправлена ​​в JDK версии 8.

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

Чем больше кода покрыто, тем лучше будет производительность.

Следующий раздел демонстрирует, как это можно реализовать.

5. Ручная реализация

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

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

public class Dummy {
    public void m() {
    }
}

Далее нам нужно создать класс, который имеет статический метод, который будет выполняться не менее 100000 раз, как только приложение запускается, и при каждом выполнении он создает новый экземпляр вышеупомянутого фиктивного класса, который мы создали ранее:

public class ManualClassLoader {
    protected static void load() {
        for (int i = 0; i < 100000; i++) {
            Dummy dummy = new Dummy();
            dummy.m();
        }
    }
}

Теперь, чтобы измерить прирост производительности , нам нужно создать основной класс. Этот класс содержит один статический блок, который содержит прямой вызов метода load () ManualClassLoader.

Внутри основной функции мы снова вызываем метод load () ManualClassLoader и фиксируем системное время в наносекундах непосредственно перед и после вызова нашей функции. Наконец, мы вычитаем эти времена, чтобы получить фактическое время выполнения.

Мы должны запустить приложение дважды; один раз с вызовом метода load () внутри статического блока и один раз без вызова этого метода:

public class MainApplication {
    static {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Warm Up time : " + (end - start));
    }
    public static void main(String[]args) {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Total time taken : " + (end - start));
    }
}

Ниже результаты воспроизводятся в наносекундах:

| =================================================== | С теплым Вверх | Нет прогрева | Разница (%) | | | 730 | | | 1256 | | | 905 | | | 706 | | | 1053 | ==================================================

Как и следовало ожидать, с прогревом подход показывает гораздо лучшую производительность, чем нормальный.

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

6. Инструменты

Мы также можем использовать несколько инструментов для разогрева JVM. Одним из наиболее известных инструментов является Java Microbenchmark Harness, JMH . Обычно он используется для микробенчмаркинга. Как только он загружен, он повторно нажимает на фрагмент кода и контролирует цикл итерации прогрева.

Чтобы использовать его, нам нужно добавить еще одну зависимость в pom.xml :

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
</dependency>

Мы можем проверить последнюю версию JMH в Central Maven Repository .

В качестве альтернативы, мы можем использовать плагин maven от JMH для создания примера проекта:

mvn archetype:generate \
    -DinteractiveMode=false \
    -DarchetypeGroupId=org.openjdk.jmh \
    -DarchetypeArtifactId=jmh-java-benchmark-archetype \
    -DgroupId=com.baeldung \
    -DartifactId=test \
    -Dversion=1.0

Далее давайте создадим метод main :

public static void main(String[]args)
  throws RunnerException, IOException {
    Main.main(args);
}

Теперь нам нужно создать метод и аннотировать его аннотацией @ Benchmark от JMH:

@Benchmark
public void init() {
   //code snippet
}

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

** 7. Тест производительности

**

За последние 20 лет большая часть вклада в Java была связана с GC (сборщик мусора) и JIT (компилятор Just In Time). Почти все онлайн-тесты производительности выполняются на JVM, уже работающей в течение некоторого времени. Тем не мение,

Однако Beihang University опубликовал сравнительный отчет с учетом времени прогрева JVM. Они использовали системы на базе Hadoop и Spark для обработки массивных данных:

ссылка:/uploads/jvm.png%20357w[]

Здесь HotTub обозначает среду, в которой была разогрета JVM.

Как видите, ускорение может быть значительным, особенно для относительно небольших операций чтения - вот почему эти данные интересно рассмотреть

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

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

This book содержит более подробную информацию и рекомендации по теме, если вы хотите продолжить.

И, как всегда, полный исходный код доступен на over на GitHub .