Como os compiladores Java AOT funcionam?

18

Existem algumas ferramentas ( Excelsior JET , etc.) que afirmam transformar aplicativos Java em executáveis ​​nativos ( *.exe). No entanto, entendo que essas ferramentas realmente estão apenas criando wrappers nativos que invocam / executam a javapartir de um shell ou linha de comando.

Se esse entendimento estiver incorreto, não vejo como poderia ser. Se uma JVM ( javaprocesso) em execução é essencialmente um intérprete de alto desempenho, carregando o bytecode de arquivos de classe Java em tempo real, não vejo como um aplicativo Java (uma coleção de arquivos de bytecode que serve como entrada para uma JVM) poderia ser verdadeiramente convertido em um executável.

Isso ocorre porque o processo da JVM é um executável nativo que recebe conjuntos de arquivos de bytecode como entrada. Mesclar esses arquivos de bytecode e o processo da JVM em um único executável nativo unificado não parece possível sem reescrever completamente a JVM e remover os trilhos da especificação da JVM.

Então, pergunto: como essas ferramentas realmente "transformam" os arquivos de classe Java em um executável nativo ou o fazem?

smeeb
fonte

Respostas:

26

Todos os programas têm um ambiente de tempo de execução. Nós tendemos a esquecer isso, mas está lá. Lib padrão para C que envolve chamadas do sistema para o sistema operacional. O Objective-C tem seu tempo de execução que envolve todas as suas mensagens.

Com Java, o tempo de execução é a JVM. A maioria das implementações de Java com as quais as pessoas estão familiarizadas é semelhante à HotSpot JVM, que é um interpretador de código de bytes e compilador JIT.

Esta não precisa ser a única implementação. Não há absolutamente nada a dizer que você não pode criar um tempo de execução padrão para Java e compilar o código no código da máquina nativo e executá-lo no tempo de execução que lida com chamadas para novos objetos em mallocs e acesso a arquivos nas chamadas do sistema na máquina. E é isso que o compilador Ahead Of Time (AOT em vez de JIT) faz. Chamar esse tempo de execução que você vai ... você poderia chamá-lo de uma implementação JVM (e não seguir a especificação JVM) ou um ambiente de tempo de execução ou lib padrão para Java. Está lá e faz essencialmente a mesma coisa.

Isso poderia ser feito reimplementando javacpara atingir a máquina nativa (foi o que o GCJ fez). Ou pode ser feito com a tradução do código de byte gerado pelo código de javacmáquina (ou byte) para outra máquina - é isso que o Android faz. Com base na Wikipedia, é o que o Excelsior JET também faz ("O compilador transforma o código de byte Java portátil em executáveis ​​otimizados para o hardware e sistema operacional (SO) desejado"), e o mesmo vale para o RoboVM .

Existem complicações adicionais com o Java, o que significa que é muito difícil fazer isso como uma abordagem exclusiva. O carregamento dinâmico de classes ( class.forName()) ou objetos com proxy requer uma dinâmica que os compiladores AOT não fornecem facilmente e, portanto, suas respectivas JVMs também devem incluir um compilador JIT (Excelsior JET) ou um intérprete (GCJ) para manipular classes que não podem ser pré-compiladas nativo.

Lembre-se, a JVM é uma especificação , com muitas implementações . A biblioteca padrão C também é uma especificação com muitas implementações diferentes.

Com o Java8, um bom trabalho foi feito na compilação do AOT. Na melhor das hipóteses, só é possível resumir a AOT em geral dentro dos limites da caixa de texto. No entanto, no JVM Language Summit de 2015 (agosto de 2015), houve uma apresentação: Java Goes AOT (vídeo do youtube). Este vídeo tem 40 minutos de duração e aborda muitos dos aspectos técnicos mais profundos e benchmarks de desempenho.


fonte
Desculpe, não sei muito sobre isso, mas isso significa que o java é nativo agora? Ou significa que há um novo sinalizador de compilador que nos permite compilar programas java para código nativo, se quisermos, e ainda temos a opção de compilar para código de bytes também?
Pavel
@ paulpaul1076 Eu sugiro assistir ao vídeo que eu vinculei. Há um pouco mais do que posso razoavelmente caber em um comentário.
4

gcj exemplo executável mínimo

Você também pode observar uma implementação de código aberto como gcj(agora obsoleta). Por exemplo, arquivo Java:

public class Main {
    public static void main(String args[]) {
        System.out.println("hello world");
    }
}

Em seguida, compile e execute com:

gcj -c Main.java
gcj --main=Main -o Main Main.o
./Main

Agora você é livre para descompilar e ver como funciona.

file Main.o diz que é um arquivo elfo.

readelf -d Main | grep NEEDED diz que depende das bibliotecas dinâmicas:

0x0000000000000001 (NEEDED)             Shared library: [libgcj.so.14]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

Portanto, libgcj.so deve estar onde a funcionalidade Java é implementada.

Você pode descompilar com:

objdump -Cdr Main.o

e veja exatamente como é implementado.

Parece muito com C ++, muitos nomes desconfortáveis ​​e chamadas de funções polimórficas indiretas.

Gostaria de saber como a coleta de lixo entra em ação. Vale a pena examinar: /programming/7100776/garbage-collection-implementation-in-compiled-languages e outras linguagens compiladas com o GC como o Go.

Testado no Ubuntu 14.04, GCC 4.8.4.

Veja também https://en.wikipedia.org/wiki/Android_Runtime , a espinha dorsal do Android 5 em diante, que faz o AOT completo para otimizar aplicativos Android.

Ciro Santilli adicionou uma nova foto
fonte