1. Введение
В этой статье мы рассмотрим компилятор Java Ahead of Time (AOT), описанный в JEP-295 и добавленный в качестве экспериментальной функции в Java 9.
Во-первых, мы увидим, что такое AOT, а во-вторых, рассмотрим простой пример. В-третьих, мы увидим некоторые ограничения AOT, и, наконец, мы обсудим некоторые возможные варианты использования.
2. Что такое предварительная компиляция?
Компиляция AOT — это один из способов повысить производительность программ Java и, в частности, время запуска JVM . JVM выполняет байт-код Java и компилирует часто выполняемый код в собственный код. Это называется компиляцией Just-in-Time (JIT). JVM решает, какой код компилировать JIT, на основе информации о профилировании, собранной во время выполнения.
Хотя этот метод позволяет JVM создавать высокооптимизированный код и повышает пиковую производительность, время запуска, вероятно, не является оптимальным, поскольку исполняемый код еще не скомпилирован JIT. AOT стремится улучшить так называемый период прогрева . Для AOT используется компилятор Graal.
В этой статье мы не будем подробно рассматривать JIT и Graal. Пожалуйста, обратитесь к другим нашим статьям, чтобы получить обзор улучшений производительности в Java 9 и 10 , а также подробное описание Graal JIT Compiler .
3. Пример
В этом примере мы будем использовать очень простой класс, скомпилируем его и посмотрим, как использовать получившуюся библиотеку.
3.1. Сборник АОТ
Давайте быстро взглянем на наш образец класса:
public class JaotCompilation {
public static void main(String[] argv) {
System.out.println(message());
}
public static String message() {
return "The JAOT compiler says 'Hello'";
}
}
Прежде чем мы сможем использовать компилятор AOT, нам нужно скомпилировать класс с помощью компилятора Java:
javac JaotCompilation.java
Затем мы передаем полученный JaotCompilation.class
компилятору AOT, который находится в том же каталоге, что и стандартный компилятор Java:
jaotc --output jaotCompilation.so JaotCompilation.class
Это создает библиотеку jaotCompilation.so
в текущем каталоге.
3.2. Запуск программы
Затем мы можем выполнить программу:
java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
Аргумент -XX:AOTLibrary
принимает относительный или полный путь к библиотеке. В качестве альтернативы мы можем скопировать библиотеку в папку lib
в домашнем каталоге Java и передать только имя библиотеки.
3.3. Проверка того, что библиотека вызывается и используется
Мы можем видеть, что библиотека действительно была загружена, добавив -XX:+PrintAOT
в качестве аргумента JVM:
java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
Вывод будет выглядеть так:
77 1 loaded ./jaotCompilation.so aot library
Однако это говорит нам только о том, что библиотека была загружена, но не о том, что она действительно использовалась. Передав аргумент -verbose
, мы увидим, что методы в библиотеке действительно вызываются:
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
Вывод будет содержать строки:
11 1 loaded ./jaotCompilation.so aot library
116 1 aot[ 1] jaotc.JaotCompilation.<init>()V
116 2 aot[ 1] jaotc.JaotCompilation.message()Ljava/lang/String;
116 3 aot[ 1] jaotc.JaotCompilation.main([Ljava/lang/String;)V
The JAOT compiler says 'Hello'
Скомпилированная библиотека AOT содержит отпечаток класса
, который должен совпадать с отпечатком файла .class .
Давайте изменим код в классе JaotCompilation.java
, чтобы он возвращал другое сообщение:
public static String message() {
return "The JAOT compiler says 'Good morning'";
}
Если мы выполним программу без AOT-компиляции модифицированного класса:
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
Тогда вывод будет содержать только:
11 1 loaded ./jaotCompilation.so aot library
The JAOT compiler says 'Good morning'
Мы видим, что методы в библиотеке вызываться не будут, так как изменился байт-код класса. Идея заключается в том, что программа всегда будет давать один и тот же результат, независимо от того, загружена скомпилированная библиотека AOT или нет.
4. Дополнительные аргументы AOT и JVM
4.1. AOT-компиляция модулей Java
Также возможно скомпилировать модуль AOT:
jaotc --output javaBase.so --module java.base
Полученная библиотека javaBase.so
имеет размер около 320 МБ, и ее загрузка занимает некоторое время. Размер можно уменьшить, выбрав пакеты и классы для компиляции AOT.
Ниже мы рассмотрим, как это сделать, однако не будем углубляться во все детали.
4.2. Выборочная компиляция с командами компиляции
Чтобы не допустить, чтобы скомпилированная AOT библиотека модуля Java стала слишком большой, мы можем добавить команды компиляции, чтобы ограничить область того, что компилируется AOT. Эти команды должны быть в текстовом файле — в нашем примере мы будем использовать файл complileCommands.txt
:
compileOnly java.lang.*
Затем мы добавляем его в команду компиляции:
jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt
Полученная библиотека будет содержать только скомпилированные AOT классы в пакете java.lang
.
Чтобы получить реальное улучшение производительности, нам нужно выяснить, какие классы вызываются во время прогрева JVM .
Этого можно добиться, добавив несколько аргументов JVM:
java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation
В этой статье мы не будем углубляться в эту технику.
4.3. Компиляция AOT одного класса
Мы можем скомпилировать один класс с аргументом –class-name
:
jaotc --output javaBaseString.so --class-name java.lang.String
Полученная библиотека будет содержать только класс String
.
4.4. Компиляция для многоуровневого
По умолчанию всегда будет использоваться скомпилированный код AOT, и JIT-компиляции для классов, включенных в библиотеку, не будет. Если мы хотим включить информацию о профилировании в библиотеку, мы можем добавить аргумент compile-for-tiered
:
jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class
Предварительно скомпилированный код в библиотеке будет использоваться до тех пор, пока байт-код не станет пригодным для JIT-компиляции.
5. Возможные варианты использования компиляции AOT
Одним из вариантов использования AOT являются короткие программы, которые завершают выполнение до того, как произойдет какая-либо JIT-компиляция.
Другой вариант использования — встроенные среды, где JIT невозможен.
На этом этапе мы также должны отметить, что скомпилированная библиотека AOT может быть загружена только из класса Java с идентичным байт-кодом, поэтому ее нельзя загрузить через JNI.
6. AOT и Amazon Lambda
Возможным вариантом использования кода, скомпилированного с помощью AOT, являются недолговечные лямбда-функции, для которых важно короткое время запуска. В этом разделе мы рассмотрим, как мы можем запускать скомпилированный AOT код Java на AWS Lambda.
Для компиляции AOT с AWS Lambda требуется, чтобы библиотека была собрана в операционной системе, совместимой с операционной системой, используемой в AWS. На момент написания статьи это Amazon Linux 2
.
Кроме того, версия Java должна совпадать. AWS предоставляет Amazon Corretto Java 11 JVM
. Чтобы иметь среду для компиляции нашей библиотеки, мы установим Amazon Linux 2
и Amazon Corretto
в Docker.
Мы не будем обсуждать все детали использования Docker и AWS Lambda, а только наметим самые важные шаги. Для получения дополнительной информации о том, как использовать Docker, обратитесь к его официальной документации здесь .
Дополнительные сведения о создании функции Lambda с помощью Java см. в нашей статье AWS Lambda With Java .
6.1. Конфигурация нашей среды разработки
Во-первых, нам нужно загрузить образ Docker для Amazon Linux 2
и установить Amazon Corretto
:
# download Amazon Linux
docker pull amazonlinux
# inside the Docker container, install Amazon Corretto
yum install java-11-amazon-corretto
# some additional libraries needed for jaotc
yum install binutils.x86_64
6.2. Скомпилируйте класс и библиотеку
Внутри нашего контейнера Docker мы выполняем следующие команды:
# create folder aot
mkdir aot
cd aot
mkdir jaotc
cd jaotc
Имя папки является только примером и, конечно, может быть любым другим именем.
package jaotc;
public class JaotCompilation {
public static int message(int input) {
return input * 2;
}
}
Следующим шагом будет компиляция класса и библиотеки:
javac JaotCompilation.java
cd ..
jaotc -J-XX:+UseSerialGC --output jaotCompilation.so jaotc/JaotCompilation.class
Здесь важно использовать тот же сборщик мусора, что и на AWS. Если наша библиотека не может быть загружена на AWS Lambda, мы можем проверить, какой сборщик мусора фактически используется, с помощью следующей команды:
java -XX:+PrintCommandLineFlags -version
Теперь мы можем создать zip-файл, содержащий нашу библиотеку и файл класса:
zip -r jaot.zip jaotCompilation.so jaotc/
6.3. Настройка AWS Lambda
Последний шаг — войти в консоль AWS Lamda, загрузить zip-файл и настроить Lambda со следующими параметрами:
- Время выполнения:
Java 11
- Обработчик:
jaotc.JaotCompilation::message
Кроме того, нам нужно создать переменную среды с именем JAVA_TOOL_OPTIONS и установить для нее значение:
-XX:+UnlockExperimentalVMOptions -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so
Эта переменная позволяет нам передавать параметры в JVM.
Последний шаг — настроить ввод для нашей лямбды. По умолчанию используется ввод JSON, который нельзя передать нашей функции, поэтому нам нужно установить его в строку, содержащую целое число, например «1».
Наконец, мы можем выполнить нашу функцию Lambda и должны увидеть в журнале, что наша скомпилированная библиотека AOT была загружена:
57 1 loaded ./jaotCompilation.so aot library
7. Заключение
В этой статье мы увидели, как AOT компилировать классы и модули Java. Поскольку это все еще экспериментальная функция, компилятор AOT не входит во все дистрибутивы. Реальные примеры все еще редко можно найти, и сообщество Java должно найти лучшие варианты использования для применения AOT.
Все фрагменты кода в этой статье можно найти в нашем репозитории GitHub .