Pontos Principais
- Fim de linha para o compilador Java JIT C2
- A nova interface de compilação JVMCI permite acoplamento com novos compiladores
- A Oracle desenvolveu o Graal, um JIT escrito em Java, como substituto
- O Graal também trabalha isoladamente e é um dos componentes principais da nova plataforma
- A GraalVM é uma VM poliglota de próxima geração que suporta diversas linguagens (e não apenas aquelas compiladas pelo JVM bytecode)
A implementação do Java da Oracle é baseada no projeto open-source OpenJDK, que inclui a VM HotSpot, que existe desde o Java 1.3. O HotSpot possui dois compiladores JIT distintos, conhecidos como C1 e C2 (algumas vezes chamados de "cliente" e "servidor"), e uma instalação moderna de Java utiliza ambos compiladores JIT durante a execução de um programa.
Um programa Java inicia no modo interpretado. Depois de umas poucas execuções, métodos invocados frequentemente são identificados e compilados - primeiro utilizando o C1 e então, se o HotSpot detectar um número ainda maior de chamadas, o método passa a ser compilado no C2. Essa estratégia é conhecida como Compilação em Níveis ("Tiered Compilation") e é a abordagem padrão do HotSpot.
Para a maioria dos apps Java, isso significa que o compilador C2 é uma das peças mais importantes do ambiente, pois produz um código de máquina extremamente otimizado que corresponde às maiores partes do programa.
O C2 tem tido enorme sucesso e pode produzir código competitivo com (ou mais rápido que) o C++, por causa de otimizações em tempo de execução que não estão disponíveis em uma compilação AOT (Ahead of Time) como o gcc ou o compilador Go.
No entanto, o C2 tem trazido pouco retorno nos últimos anos e não recebe grandes melhorias há um bom tempo. Além disso, o código no C2 tem se tornado difícil de manter e estender, sendo muito difícil para um novo engenheiro acelerar o código base escrito em um dialeto específico do C++.
De fato, existe uma ampla crença (em empresas como o Twitter, e em experts como Cliff Click) de que não é mais possível incluir grandes melhorias com o projeto atual. Isso significa que qualquer melhoria no C2 teria de ser periférica.
Uma das únicas áreas que viu melhorias nos releases recentes foi o uso de mais recursos intrínsecos do JVM, uma técnica descrita na documentação (para a anotação @HotSpotIntrinsicCandidate) como:
Um método é intrínseco se a VM HotSpot substitui o método anotado com um assembly escrito manualmente e/ou um compilador IR escrito manualmente - um compilador intrínseco para melhorar a performance.
Quando a JVM inicia, o processador onde está sendo executado é sondado. Isso permite que a JVM veja exatamente quais recursos da CPU estão disponíveis. Ele constrói uma tabela com os recursos intrínsecos que são específicos do processador em uso. Isso significa que a JVM pode tirar o máximo de vantagem das capacidades do hardware.
Isso é diferente da compilação AOT, que precisa compilar para um chip genérico e fazer suposições conservadoras sobre quais recursos estão disponíveis, pois um binário compilador em AOT irá quebrar se tentar rodar instruções não suportadas no CPU em que está sendo executado.
O HotSpot já suporta alguns intrínsecos - por exemplo, a conhecida instrução /CAS (Compare-And-Swap) que é usada para implementar funcionalidades como os inteiros atômicos (atomic integers). Em quase todos os processadores modernos, isso é implementado usando uma única instrução de hardware.
Os intrínsecos são pré-conhecidos pela JVM e dependem de serem suportados por recursos específicos dos sistemas operacionais ou das arquiteturas de CPU. Isso faz deles específicos à plataforma, e nem todos os intrínsecos são suportados em todas as plataformas.
No geral, os intrínsecos devem ser reconhecidos como pontos fixos e não como técnicas gerais. Eles têm a vantagem de serem poderosos, leves e flexíveis, mas têm potencial de alto custo no desenvolvimento e manutenção por terem de ser suportados em múltiplas arquiteturas.
Portanto, apesar do progresso feito com os intrínsecos, em todas as intenções e propósitos, o C2 chegou ao fim de seu ciclo de vida e precisa ser substituído.
A Oracle anunciou recentemente o primeiro lançamento da GraalVM, um projeto de pesquisa que pode substituir completamente o HotSpot.
Para os desenvolvedores Java, o Graal pode ser pensado como diversos projetos separados, mas ainda conectados - é um novo compilador JIT para o HotSpot, e também uma nova VM poliglota. Iremos nos referir ao compilador JIT como Graal e à nova VM como GraalVM.
O objetivo geral do Graal é repensar como a compilação funciona no Java (e no caso da GraalVM, em outras linguagens também). A observação básica do Graal começa bem simples:
Um compilador (JIT) para Java transforma bytecode em código de máquina - nos termos do Java isso é apenas uma transformação de um byte[] para outro byte[] - assim, o que poderia acontecer se o código transformado fosse escrito em Java?
Acontece que há algumas grandes vantagens ao escrever um compilador em Java, como:
- Muito menos barreiras para a entrada de novos engenheiros de compilação
- Segurança de memória no compilador
- Capacidade de aproveitar as ferramentas maduras do Java no desenvolvimento de compiladores
- Muito mais rápido de prototipar novas funcionalidades para compiladores
- O compilador pode ser independente do HotSpot
- O compilador pode ser capaz de compilar a si mesmo, para produzir uma versão compilada em JIT mais rápida de si mesmo
O Graal utiliza a nova JVMCI (JVM Compiler Interface, disponível como JEP 243 para acoplar ao HotSpot), mas isso também pode ser usado como uma grande parte da GraalVM. A tecnologia está presente hoje, apesar do Java 10 continuar mais como uma tecnologia experimental. As mudanças para habilitar o novo compilador JIT são:
-XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler
Isso significa que há três formas diferentes de rodar um programa - tanto com compiladores regulares, ou com a versão JVMCI do Graal em Java 10 e, finalmente, com a própria GraalVM.
Para ver os efeitos do Graal, vamos usar um exemplo simples, que apesar de lento é suficiente para ver o compilador iniciar - uma função hash para string:
package kathik;
public final class StringHash {
public static void main(String[] args) {
StringHash sh = new StringHash();
sh.run();
}
void run() {
for (int i=1; i<2_000; i++) {
timeHashing(i, 'x');
}
}
void timeHashing(int length, char c) {
final StringBuilder sb = new StringBuilder();
for (int j = 0; j < length * 1_000_000; j++) {
sb.append(c);
}
final String s = sb.toString();
final long now = System.nanoTime();
final int hash = s.hashCode();
final long duration = System.nanoTime() - now;
System.out.println("Length: "+ length +" took: "+ duration +" ns");
}
}
Podemos executar esse código com a flag PrintCompilation setada da forma usual para ver quais métodos são compilados (ela também fornece uma base para comparação com o Graal):
java -XX:+PrintCompilation -cp target/classes/ kathik.StringHash > out.txt
Para ver os efeitos do Graal como um compilador rodando em Java 10:
java -XX:+PrintCompilation \
-XX:+UnlockExperimentalVMOptions \
-XX:+EnableJVMCI \
-XX:+UseJVMCICompiler \
-cp target/classes/ \
kathik.StringHash > out-jvmci.txt
and for GraalVM:
java -XX:+PrintCompilation \
-cp target/classes/ \
kathik.StringHash > out-graal.txt
Três arquivos de saída serão gerados - e se parecerão com isso quando truncados à saída gerada ao rodar as primeiras 200 iterações do timeHashing():
$ ls -larth out*
-rw-r--r-- 1 ben staff 18K 4 Jun 13:02 out.txt
-rw-r--r-- 1 ben staff 591K 4 Jun 13:03 out-graal.txt
-rw-r--r-- 1 ben staff 367K 4 Jun 13:03 out-jvmci.txt
Como esperado, rodar utilizando o Graal cria muito mais saídas - isso por causa das diferentes saídas do PrintCompilation. Isso não deve ser uma surpresa - o objetivo do Graal é que o compilador JIT seja uma das primeiras coisas a serem compiladas, portanto haverá muita preparação do compilador JIT nos primeiros segundos após o início da VM.
Vamos olhar algumas das primeiras saídas do JIT executando Java 10 usando o compilador Graal (na forma usual do PrintCompilation):
$ grep graal out-jvmci.txt | head
229 293 3 org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::adjustCompilationLevelInternal (70 bytes)
229 294 3 org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::checkGraalCompileOnlyFilter (95 bytes)
231 298 3 org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::adjustCompilationLevel (9 bytes)
353 414 ! 1 org.graalvm.compiler.serviceprovider.JDK9Method::invoke (51 bytes)
354 415 1 org.graalvm.compiler.serviceprovider.JDK9Method::checkAvailability (37 bytes)
388 440 1 org.graalvm.compiler.hotspot.HotSpotForeignCallLinkageImpl::asJavaType (32 bytes)
389 441 1 org.graalvm.compiler.hotspot.word.HotSpotWordTypes::isWord (31 bytes)
389 443 1 org.graalvm.compiler.core.common.spi.ForeignCallDescriptor::getResultType (5 bytes)
390 445 1 org.graalvm.util.impl.EconomicMapImpl::getHashTableSize (43 bytes)
390 447 1 org.graalvm.util.impl.EconomicMapImpl::getRawValue (11 bytes)
Pequenas experiências como essa devem ser tratadas com cautela. Por exemplo, os efeitos em uma tela com tanta compilação prévia pode distorcer a performance de preparação. Não apenas isso, com o tempo os buffers alocados de cada string crescente se tornarão tão grande que terão de ser alocados nas Regiões Humongous (regiões especiais reservadas pelo coletor G1 somente para objetos grandes) - assim como ambos, Java 10 e GraalVM, utilizam o coletor G1 por padrão. Isso significa que o perfil de garbage collection do G1 será dominado pelas coleções G1 Humongous depois de algum tempo, o que realmente não é uma situação comum.
Antes de discutir a GraalVM, vale a pena notar que existe uma outra forma do compilador Graal ser usado no Java 10 - o modo de compilação AOT (Ahead-of-Time).
Lembrando que o Graal (como compilador) foi escrito do zero como um novo compilador que respeita uma nova e limpa interface (JVMCI). Esse modelo significa que o Graal pode se integrar com o HotSpot sem estar acoplado a ele.
Ao invés de utilizar uma abordagem dirigida a perfis para compilar somente os métodos mais importantes, podemos considerar utilizar o Graal para uma compilação total de todos os métodos em um modo offline sem executar o código. Essa é a capacidade referenciada como "Compilação AOT" (Ahead-of-Time Compilation), JEP 295.
Com o ambiente do HotSpot, podemos usar isso para produzir objetos e bibliotecas compartilhadas (.so no Linux ou .dylib no Mac):
$ jaotc --output libStringHash.dylib kathik/StringHash.class
Podemos utilizar o código compilado em execuções futuras:
$ java -XX:AOTLibrary=./libStringHash.dylib kathik.StringHash
Esta utilização do Graal tem um único objetivo - aumentar a velocidade de inicialização até que a abordagem regular de compilação em níveis (Tiered Compilation) no HotSpot assuma. Em termos absolutos, em uma aplicação completa, espera-se que a compilação JIT supere a compilação AOT em situações reais, apesar dos detalhes dependerem do tamanho da carga.
A tecnologia de compilação AOT ainda é atual, e tecnicamente somente é suportada (mesmo experimentalmente) em linux / x64. Por exemplo, ao tentar compilar o módulo java.base no Mac, os seguintes erros ocorrem (apesar de uma .dylib ser produzida):
$ jaotc --output libjava.base.dylib --module java.base
Error: Failed compilation: sun.reflect.misc.Trampoline.invoke(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: java.lang.Error: Trampoline must not be defined by the bootstrap classloader
at parsing java.base@10/sun.reflect.misc.Trampoline.invoke(MethodUtil.java:70)
Error: Failed compilation: sun.reflect.misc.Trampoline.<clinit>()V: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: java.lang.NoClassDefFoundError: Could not initialize class sun.reflect.misc.Trampoline
at parsing java.base@10/sun.reflect.misc.Trampoline.<clinit>(MethodUtil.java:50)
Esses erros podem ser controlados utilizando um arquivo de diretivas do compilador para excluir certos métodos da compilação AOT (veja na página do JEP 295 para mais detalhes).
Apesar dos erros de compilação, podemos continuar tentando executar o código do módulo base compilado em AOT junto ao código do usuário, como esse:
java -XX:+PrintCompilation \
-XX:AOTLibrary=./libStringHash.dylib,libjava.base.dylib \
kathik.StringHash
Ao passar o PrintCompilation, podemos ver o quanto de atividade na compilação JIT é produzida - que agora é praticamente nenhuma. Somente alguns métodos essenciais necessários para o bootstrap são compilados pelo JIT:
111 1 n 0 java.lang.Object::hashCode (native)
115 2 n 0 java.lang.Module::addExportsToAllUnnamed0 (native) (static)
Como resultado, podemos concluir que nossa app Java simples está agora rodando em quase 100% na compilação AOT.
Voltanto à GraalVm, vamos olhar uma das funcionalidades principais que a plataforma oferece - a habilidade de embarcar completamente linguagens poliglotas em aplicações Java rodando dentro da GraalVM.
Isso pode ser considerado ao equivalente, ou substituto do JSR 223 (Scripting for the Java Platform), mas a abordagem do Graal vai muito mais além e profundamente do que as tecnologias anteriores do HotSpot.
O recurso se baseia na GraalVM e no Graal SDK - que é fornecido como parte padrão do classpath da GraalVM, mas deve ser incluído explicitamente nos projetos de IDE, por exemplo:
<dependency>
<groupId>org.graalvm</groupId>
<artifactId>graal-sdk</artifactId>
<version>1.0.0-rc1</version>
</dependency>
O exemplo mais simples é um Hello World - vamos usar a implementação Javascript que a GraalVM entrega como padrão:
import org.graalvm.polyglot.Context;
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello World: Java!");
Context context = Context.create();
context.eval("js", "print('Hello World: JavaScript!');");
}
}
Ele roda como esperado na GraalVM, mas ao tentar rodar sobre o Java 10, mesmo fornecendo o Graal SDK, é produzido (sem surpresas) o erro:
$ java -cp target/classes:$HOME/.m2/repository/org/graalvm/graal-sdk/1.0.0-rc1/graal-sdk-1.0.0-rc1.jar kathik.HelloPolyglot
Hello Java!
Exception in thread "main" java.lang.IllegalStateException: No language and polyglot implementation was found on the classpath. Make sure the truffle-api.jar is on the classpath.
at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:548)
at org.graalvm.polyglot.Engine$PolyglotInvalid.buildEngine(Engine.java:538)
at org.graalvm.polyglot.Engine$Builder.build(Engine.java:367)
at org.graalvm.polyglot.Context$Builder.build(Context.java:528)
at org.graalvm.polyglot.Context.create(Context.java:294)
at kathik.HelloPolyglot.main(HelloPolyglot.java:8)
Isso significa que o Truffle está restrito a rodar somente na GraalVM (pelo menos neste momento).
Uma forma de capacidade poliglota existe desde o Java 6, com a introdução do Scripting API, que foi bastante melhorado no Java 8 com a chegada do Nashorn, a implementação JavaScript baseada em invokedynamic.
O que diferencia a tecnologia da GraalVM é que o ecossistema inclui explicitamente um SDK e ferramentas de suporte para implementar múltiplas linguagens e executá-los como co-iguais e elementos interoperáveis da mesma VM.
As chaves para esse passo a frente são o componente chamado Truffle e uma VM básica e simples, a SubstrateVM, capaz de executar bytecode da JVM.
O Truffle fornece um SDK e ferramentas para criar novas implementações de linguagens. Essa é a abordagem geral:
- Iniciar com uma gramática da linguagem
- Aplicar um gerador parser (ex.: Coco/R)
- Usar o Maven para construir um interpretador e uma execução simples da linguagem
- Rodar a implementação da linguagem resultante na GraalVM
- Aguardar o Graal (no modo JIT) iniciar para automaticamente melhorar a performance da nova linguagem
- (Opcional) Usar o Graal no modo AOT para compilar o interpretador em um iniciador nativo
Por padrão, a GraalVM vem com suporte ao bytecodes JVM, ao JavaScript e à LLVM. Se tentarmos chamar outra linguagem, como o Ruby:
context.eval("ruby", "puts \"Hello World: Ruby\"");
então a GraalVM dispara uma exceção em tempo de execução:
Exception in thread "main" java.lang.IllegalStateException: A language with id 'ruby' is not installed. Installed languages are: [js, llvm].
at com.oracle.truffle.api.vm.PolyglotEngineImpl.requirePublicLanguage(PolyglotEngineImpl.java:559)
at com.oracle.truffle.api.vm.PolyglotContextImpl.requirePublicLanguage(PolyglotContextImpl.java:738)
at com.oracle.truffle.api.vm.PolyglotContextImpl.eval(PolyglotContextImpl.java:715)
at org.graalvm.polyglot.Context.eval(Context.java:311)
at org.graalvm.polyglot.Context.eval(Context.java:336)
at kathik.HelloPolyglot.main(HelloPolyglot.java:10)
Para usar a (ainda em beta) versão do Truffle para Ruby (ou outra linguagem), precisamos baixá-la e instalá-la. Para o Graal RC1 (a ser substituído em breve pelo RC2), devemos executar:
gu -v install -c org.graalvm.ruby
Note que isso requer um sudo se a GraalVM tiver sido instalado amplamente no sistema como um padrão $JAVA_HOME para múltiplos usuários. Se estiver utilizando o non-OSS EE edition da GraalVM (a única disponível para o Mac), então podemos ir um passo além - e o interpretador Truffle pode ser convertido em código nativo.
Reconstruir a imagem nativa (inicializador) para a linguagem irá melhorar a performance, mas isso requer usar as ferramentas de linha de comando, como essa (considerando a GraalVM instalada amplamente no sistema, com uma raiz):
$ cd $JAVA_HOME
$ sudo jre/lib/svm/bin/rebuild-images ruby
Isso ainda está em desenvolvimento, e tem alguns passos manuais, mas a equipe de desenvolvimento espera tornar o processo mais tranquilo ao longo do tempo.
Se qualquer problema é encontrado com o rebuild de componentes nativos, não se preocupe - ele deve continuar funcionando sem reconstruir imagens nativas.
Vamos ver um exemplo mais complexo de código poliglota:
Context context = Context.newBuilder().allowAllAccess(true).build();
Value sayHello = context.eval("ruby",
"class HelloWorld\n" +
" def hello(name)\n" +
" \"Hello #{name}\"\n" +
" end\n" +
"end\n" +
"hi = HelloWorld.new\n" +
"hi.hello(\"Ruby\")\n");
String rubySays = sayHello.as(String.class);
Value jsFunc = context.eval("js",
"function(x) print('Hello World: JavaScript with '+ x +'!');");
jsFunc.execute(rubySays);
Este código é um pouco difícil de ler, mas usa ambos: TruffleRuby e JavaScript. Primeiro, chamamos esse trecho Ruby:
class HelloWorld
def hello(name)
"Hello #{name}"
end
end
hi = HelloWorld.new
hi.hello("Ruby")
Isso cria uma nova classe Ruby, define um método nela, e então instancia um objeto Ruby e finalmente chama o método hello(). Este método retorna uma string (Ruby), que é retornada como uma string Java em tempo de execução.
Então podemos criar uma simples função anônima JavaScript, que se parece com:
function(x) print('Hello World: JavaScript with '+ x +'!');
Chamamos essa função via execute() e passamos no resultado de nossa chamada Ruby para a função que imprime na tela, a partir do tempo de execução do JS.
Note que quando criamos o objeto de Contexto, devemos permitir acesso estendido ao contexto. Isso é para o Ruby - não precisamos disso para o JS - daí a construção mais complexa na preparação. Isso é uma limitação da atual implementação Ruby, e poderá ser futuramente removida.
Vamos ver um exemplo poliglota final:
Value sayHello = context.eval("ruby",
"class HelloWorld\n" +
" def hello(name)\n" +
" \"Hello Ruby: #{name}\"\n" +
" end\n" +
"end\n" +
"hi = HelloWorld.new\n" +
"hi");
Value jsFunc = context.eval("js",
"function(x) print('Hello World: JS with '+ x.hello('Cross-call') +'!');");
jsFunc.execute(sayHello);
Nessa versão, estamos retornando um objeto Ruby, não apenas uma string. Além do mais, não estamos convertendo para um tipo Java, e sim passando diretamente a essa função JS:
function(x) print('Hello World: JS with '+ x.hello('Cross-call') +'!');
Funciona, e produz as saídas esperadas:
Hello World: Java!
Hello World: JS with Hello Ruby: Cross-call!
Isso significa que a execução JS pode chamar um método estrangeiro sobre um objeto em um tempo de execução separado, com conversão sem interrupção (ao menos nesse caso simples).
Essa habilidade de substituição através de linguagens que têm semânticas e sistemas de tipos bem diferentes tem sido discutida pelos engenheiros da JVM por um longo tempo (pelo menos 10 anos), e com a chegada da GraalVM, isso tomou um passo significativo para se tornar tendência.
Vamos olhar rapidamente como esses objetos estrangeiros são representados na GraalVM, usando um pouco de JS para mostrar o objeto Ruby retornado:
function(x) print('Hello World: JS with '+ x +'!');
Isso resulta em:
Hello World: JS with foreign {is_a?: DynamicObject@540a903b<Method>, extend: DynamicObject@238acd0b<Method>, protected_methods: DynamicObject@34e20e6b<Method>, public_methods: DynamicObject@15ac59c2<Method>, ...}!
mostrando que o objeto estrangeiro é representado como um pacote de objetos do DynamicObject, que irá delegar as operações semânticas, em muitos casos de volta ao início da execução do objeto.
Para concluir este artigo, devemos dizer um pouco sobre benchmarks e licenciamento. Deve ser claro que apesar das enormes promessas do Graal e da GraalVM, atualmente estão em estágio inicial/experimental.
Ainda não foram otimizados ou concluídos para propósitos gerais, e ainda levará tempo para alcançar paridade com o HotSpot / C2. Micro benchmarks também são, geralmente, enganosos - podem indicar uma direção em algumas circunstâncias, mas no fim, somente benchmarks com usuários finais de aplicações completas em produção importam para análises de performance.
Uma forma de pensar sobre isso é que o C2 é essencialmente um ambiente máximo de performance e está no fim de seu ciclo de vida. O Graal nos dá a oportunidade de quebrar esse máximo e prosseguir para uma região nova e melhor - e potencialmente reescrever pelo caminho muito do que acreditávamos saber sobre design de VMs e compiladores. Mas ainda é uma tecnologia imatura - e é muito improvável que se torne tendência por muito anos ainda.
Isso significa que qualquer teste de desempenho feito hoje deve ser analisado com muita cautela. Testes comparativos de desempenho (especialmente com o HotSpot + C2 Vs. GraalVM) são como comparar maçãs e laranjas - uma execução madura, de nível de produção Vs. uma em estágio experimental muito inicial.
Também devemos destacar que as licenças para a GraalVM devem ser diferentes das existentes. Quando a Oracle comprou a Sun, adquiriram o HotSpot como um produto existente e muito maduro, licenciado como Software Livre. Houveram tentativas limitadas de adicionar valor e monetizar com o HotSpot - ex.: a opção UnlockCommercialFeatures. Com a retirada dessas funcionalidades (ex.: o open source Mission Control) então é justo dizer que esse modelo não foi um grande sucesso comercial.
O Graal é diferente - começou como um projeto de pesquisa da Oracle que agora está se tornando um produto em produção. A Oracle tem investido grandes somas para fazer do Graal uma realidade - e os indivíduos e equipes necessários para o projeto são escassos e raramente baratos. Como ela é baseada em uma tecnologia subjacente diferente, a Oracle tem a liberdade de usar um modelo comercial diferente para o HotSpot e tentar monetizar a GraalVM em uma variedade maior de clientes - incluindo aqueles que atualmente não pagam pela execução do HotSpot. É até possível que a Oracle decida que alguns recursos da GraalVM só serão disponibilizados para clientes rodando no Oracle Cloud.
Por ora, a Oracle está entregando uma licença GPL Community Edition (CE), que é livre para uso em desenvolvimento e produção, e uma Enterprise Edition (EE) que é livre para desenvolvimento e para avaliação em uso. Ambas as versões podem ser baixadas do site GraalVM da Oracle, onde informações adicionais podem ser encontradas.
Sobre o Autor
Ben Evans é co-fundador da jClarity, uma empresa de otimização de desempenho da JVM. Também é organizador do LJC (London's JUG) e membro do Comitê Executivo do JCP, ajudando a definir padrões para o ecossistema Java. É um Java Champion; 3 vezes palestrante JavaOne Rockstar; autor do livro "The Well-Grounded Java Developer", a nova edição do "Java in a Nutshell" e "Optimizing Java". Também é um palestrante regular sobre a plataforma Java, desempenho, arquitetura, concorrência, startups e tópicos relacionados. Às vezes está disponível para palestras, aulas, escrever e consultorias - por favor, entre em contato para mais detalhes.
.