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

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

Узнайте, что значит разогреть JVM и как это сделать.

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

1. Обзор

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

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

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

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

  1. Загрузка класса начальной загрузки:Загрузчик классов начальной загрузки ” загружает код Java и основные классы Java, такие как java.lang.Объект в память. Эти загруженные классы находятся в JRE\lib\rt.jar .
  2. Загрузка класса расширения : ExtClassLoader отвечает за загрузку всех файлов JAR, расположенных по пути java.ext.dirs . В приложениях, не основанных на Maven или Gradle, где разработчик добавляет JAR вручную, все эти классы загружаются на этом этапе.
  3. Загрузка класса приложения : AppClassLoader загружает все классы, расположенные в пути к классу приложения.

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

3. Что такое разогрев JVM

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

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

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

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

4. Многоуровневая Компиляция

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

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

-XX:CompileThreshold -XX:TieredCompilation

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

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

Приложения, работающие на JBoss и JDK версии 7 с включенным аргументом виртуальной машины, имеют тенденцию к сбою через некоторое время из-за задокументированной ошибки |. Проблема была исправлена в 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 () ручного загрузчика классов.

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

Мы должны запустить приложение дважды: один раз с вызовом метода 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));
    }
}

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

С Разминкой Никакой Разминки Разница(%)
1220056 8903640 730
1083797 13609530 1256
1026025 9283837 905
1024047 7234871 706
868782 9146180 1053

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

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

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

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

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


    org.openjdk.jmh
    jmh-core
    1.28


    org.openjdk.jmh
    jmh-generator-annprocess
    1.28

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

В качестве альтернативы мы можем использовать плагин 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);
}

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

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

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

7. Контрольные показатели производительности

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

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

Здесь Гидромассажная ванна обозначает среду, в которой прогревался СПМ.

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

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

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

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

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