Compilação antes do tempo (AoT)
1. Introdução
Neste artigo, veremos o compilador Java Ahead of Time (AOT), que é descrito emJEP-295e foi adicionado como um recurso experimental em Java 9.
Primeiro, veremos o que é AOT e, em segundo lugar, veremos um exemplo simples. Terceiro, veremos algumas restrições do AOT e, por último, discutiremos alguns possíveis casos de uso.
2. O que é compilação antecipada?
AOT compilation is one way of improving the performance of Java programs and in particular the startup time of the JVM. A JVM executa o bytecode Java e compila o código executado com frequência no código nativo. Isso é chamado de compilação Just-in-Time (JIT). A JVM decide qual código compilar o JIT com base nas informações de criação de perfil coletadas durante a execução.
Embora essa técnica permita que a JVM produza código altamente otimizado e melhore o desempenho máximo, o tempo de inicialização provavelmente não é ideal, pois o código executado ainda não foi compilado pelo JIT. AOT aims to improve this so-called warming-up period. O compilador usado para AOT é Graal.
In this article, we won’t look at JIT and Graal in detail. Consulte nossos outros artigos para uma visão geral deperformance improvements in Java 9 and 10, bem comodeep dive into the Graal JIT Compiler.
3. Exemplo
Para este exemplo, vamos usar uma classe muito simples, compilá-la e ver como usar a biblioteca resultante.
3.1. Compilação AOT
Vamos dar uma olhada rápida em nosso exemplo de classe:
public class JaotCompilation {
public static void main(String[] argv) {
System.out.println(message());
}
public static String message() {
return "The JAOT compiler says 'Hello'";
}
}
Antes de podermos usar o compilador AOT, precisamos compilar a classe com o compilador Java:
javac JaotCompilation.java
Em seguida, passamos oJaotCompilation.class resultante para o compilador AOT, que está localizado no mesmo diretório que o compilador Java padrão:
jaotc --output jaotCompilation.so JaotCompilation.class
Isso produz a bibliotecajaotCompilation.so no diretório atual.
3.2. Executando o Programa
Podemos então executar o programa:
java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
O argumento-XX:AOTLibrary aceita um caminho relativo ou completo para a biblioteca. Como alternativa, podemos copiar a biblioteca para a pastalib no diretório inicial do Java e passar apenas o nome da biblioteca.
3.3. Verificando se a biblioteca é chamada e usada
Podemos ver que a biblioteca foi realmente carregada adicionando-XX:+PrintAOT como um argumento JVM:
java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
A saída será semelhante a:
77 1 loaded ./jaotCompilation.so aot library
No entanto, isso nos diz apenas que a biblioteca foi carregada, mas não que ela realmente foi usada. Ao passar o argumento-verbose, podemos ver que os métodos na biblioteca são realmente chamados:
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
A saída conterá as linhas:
11 1 loaded ./jaotCompilation.so aot library
116 1 aot[ 1] jaotc.JaotCompilation.()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'
A biblioteca compilada AOT contém umclass fingerprint, que deve corresponder à impressão digital do arquivo.class.
Vamos mudar o código na classeJaotCompilation.java para retornar uma mensagem diferente:
public static String message() {
return "The JAOT compiler says 'Good morning'";
}
Se executarmos o programa sem AOT compilar a classe modificada:
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
Então a saída conterá apenas:
11 1 loaded ./jaotCompilation.so aot library
The JAOT compiler says 'Good morning'
We can see that the methods in the library won’t be called, as the bytecode of the class has changed. A ideia por trás disso é que o programa sempre produzirá o mesmo resultado, não importa se uma biblioteca compilada AOT está carregada ou não.
4. Mais argumentos de AOT e JVM
4.1. Compilação AOT de módulos Java
Também é possível compilar AOT um módulo:
jaotc --output javaBase.so --module java.base
A biblioteca resultantejavaBase.so tem cerca de 320 MB e leva algum tempo para carregar. O tamanho pode ser reduzido selecionando os pacotes e classes a serem compilados no AOT.
Veremos como fazer isso a seguir, no entanto, não vamos nos aprofundar em todos os detalhes.
4.2. Compilação seletiva com comandos de compilação
To prevent the AOT compiled library of a Java module from becoming too large, we can add compile commands to limit the scope of what gets AOT compiled. Esses comandos precisam estar em um arquivo de texto - em nosso exemplo, usaremos o arquivocomplileCommands.txt:
compileOnly java.lang.*
Em seguida, adicionamos ao comando compile:
jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt
A biblioteca resultante conterá apenas as classes compiladas AOT empackage java.lang.
To gain real performance improvement, we need to find out which classes are invoked during the warm-up of the JVM.
Isso pode ser alcançado adicionando vários argumentos da JVM:
java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation
Neste artigo, não vamos nos aprofundar nessa técnica.
4.3. Compilação AOT de uma única classe
Podemos compilar uma única classe com o argumento–class-name:
jaotc --output javaBaseString.so --class-name java.lang.String
A biblioteca resultante conterá apenas a classeString.
4.4. Compilar para camadas
Por padrão, o código compilado AOT sempre será usado e nenhuma compilação JIT ocorrerá para as classes incluídas na biblioteca. If we want to include the profiling information in the library, we can add the argument compile-for-tiered:
jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class
O código pré-compilado na biblioteca será usado até que o bytecode se torne elegível para a compilação JIT.
5. Possíveis casos de uso para compilação AOT
Um caso de uso do AOT são os programas de execução curta, que concluem a execução antes que ocorra qualquer compilação JIT.
Outro caso de uso são ambientes incorporados, onde JIT não é possível.
Neste ponto, também precisamos observar que a biblioteca compilada AOT só pode ser carregada de uma classe Java com bytecode idêntico, portanto, não pode ser carregada via JNI.
6. Conclusão
Neste artigo, vimos como AOT compilar classes e módulos Java. Como este ainda é um recurso experimental, o compilador AOT não faz parte de todas as distribuições. Ainda é raro encontrar exemplos reais, e caberá à comunidade Java encontrar os melhores casos de uso para aplicar o AOT.
Todos os trechos de código neste artigo podem ser encontrados em nossoGitHub repository.