Como aquecer a JVM

Como aquecer a JVM

*1. Visão geral *

A JVM é uma das mais antigas e poderosas máquinas virtuais já construídas.

Neste artigo, veremos rapidamente o que significa aquecer uma JVM e como fazê-lo.

===* 2. Noções básicas da arquitetura da JVM *

Sempre que um novo processo da JVM é iniciado, todas as classes necessárias são carregadas na memória por uma instância do ClassLoader. Esse processo ocorre em três etapas:

* Carregamento da classe de extensão *: o ExtClassLoader é responsável por carregar todos os arquivos JAR localizados no caminho java.ext.dirs. Em aplicativos não baseados em Maven ou Gradle, em que um desenvolvedor adiciona JARs manualmente, todas essas classes são carregadas durante essa fase.
  1. Carregamento da classe do aplicativo : o AppClassLoader carrega todas as classes localizadas no caminho da classe do aplicativo.

Esse processo de inicialização é baseado em um esquema de carregamento lento.

*3. O que está aquecendo a JVM *

Depois que o carregamento da classe é concluído, todas as classes importantes (usadas no momento do início do processo) são enviadas para https://www.ibm.com/support/knowledgecenter/en/SSAW57_8.5.5/com.ibm.websphere.nd .doc/ae/rdyn_tunediskcache.html [Cache da JVM (código nativo)] - que os torna acessíveis mais rapidamente durante o tempo de execução. Outras classes são carregadas por solicitação.

A primeira solicitação feita a um aplicativo da Web Java geralmente é substancialmente mais lenta que o tempo médio de resposta durante a vida útil do processo. Esse período de aquecimento geralmente pode ser atribuído ao carregamento lento da classe e à compilação just-in-time.

Tendo isso em mente, para aplicativos de baixa latência, precisamos armazenar em cache todas as classes antecipadamente - para que elas estejam disponíveis instantaneamente quando acessadas em tempo de execução.

*Esse processo de ajuste da JVM é conhecido como aquecimento.*

*4. Compilação em camadas *

Graças à arquitetura de som da JVM, os métodos usados ​​com frequência são carregados no cache nativo durante o ciclo de vida do aplicativo.

Podemos usar essa propriedade para forçar o carregamento de métodos críticos no cache quando um aplicativo é iniciado. Nessa medida, precisamos definir um argumento da VM chamado* _Tiered Compilation _: *

-XX:CompileThreshold -XX:TieredCompilation

Normalmente, a VM usa o intérprete para coletar informações de criação de perfil sobre métodos que são alimentados no compilador. No esquema em camadas, além do intérprete, o compilador do cliente é usado para gerar versões compiladas de métodos que coletam informações de criação de perfil sobre si mesmos.

Como o código compilado é substancialmente mais rápido que o código interpretado, o programa é executado com melhor desempenho durante a fase de criação de perfil.

Os aplicativos em execução no JBoss e JDK versão 7 com esse argumento da VM ativado tendem a falhar após algum tempo devido a um bug documentado. O problema foi corrigido no JDK versão 8.

Outro ponto a ser observado aqui é que, para forçar o carregamento, precisamos garantir que todas (ou a maioria) das classes que serão executadas precisam ser acessadas. É semelhante a determinar a cobertura do código durante o teste de unidade. Quanto mais código for coberto, melhor será o desempenho.

A próxima seção demonstra como isso pode ser implementado.

===* 5. Implementação manual *

Podemos implementar uma técnica alternativa para aquecer a JVM. Nesse caso, um simples aquecimento manual pode incluir a repetição da criação de diferentes classes milhares de vezes, assim que o aplicativo é iniciado.

Primeiro, precisamos criar uma classe fictícia com um método normal:

public class Dummy {
    public void m() {
    }
}

Em seguida, precisamos criar uma classe que tenha um método estático que será executado pelo menos 100000 vezes assim que o aplicativo iniciar e, a cada execução, cria uma nova instância da classe dummy mencionada anteriormente, que criamos anteriormente:

public class ManualClassLoader {
    protected static void load() {
        for (int i = 0; i < 100000; i++) {
            Dummy dummy = new Dummy();
            dummy.m();
        }
    }
}

Agora, para* medir o ganho de desempenho *, precisamos criar uma classe principal. Esta classe contém um bloco estático que contém uma chamada direta ao método load () _ _ManualClassLoader.

Dentro da função principal, fazemos uma chamada para o método load () _ManualClassLoader mais uma vez e capturamos o tempo do sistema em nanossegundos antes e depois da nossa chamada de função. Finalmente, subtraímos esses tempos para obter o tempo real de execução.

Temos que executar o aplicativo duas vezes; uma vez com a chamada do método _load () _ dentro do bloco estático e uma vez sem essa chamada do método:

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));
    }
}

Abaixo, os resultados são reproduzidos em nanossegundos:

With Warm Up

No Warm Up

*Difference(%) *

1220056

8903640

730

1083797

13609530

1256

1026025

9283837

905

1024047

7234871

706

868782

9146180

1053

Como esperado, a abordagem de aquecimento mostra um desempenho muito melhor que o normal.

Obviamente, esse é um* ponto de referência muito simplista *e fornece apenas algumas informações no nível da superfície sobre o impacto dessa técnica. Além disso, é importante entender que, com um aplicativo do mundo real, precisamos nos aquecer com os caminhos de código típicos no sistema.

===* 6. Ferramentas *

Também podemos usar várias ferramentas para aquecer a JVM. Uma das ferramentas mais conhecidas é o Java Microbenchmark Harness, JMH. Geralmente é usado para micro-benchmarking. Depois de carregado,* ele atinge repetidamente um trecho de código e monitora o ciclo de iteração de aquecimento. *

Para usá-lo, precisamos adicionar outra dependência ao pom.xml:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
</dependency>

Podemos verificar a versão mais recente do JMH em Repositório Central do Maven.

Como alternativa, podemos usar o plugin maven do JMH para gerar um projeto de amostra:

mvn archetype:generate \
    -DinteractiveMode=false \
    -DarchetypeGroupId=org.openjdk.jmh \
    -DarchetypeArtifactId=jmh-java-benchmark-archetype \
    -DgroupId=com. \
    -DartifactId=test \
    -Dversion=1.0

Em seguida, vamos criar um método main:

public static void main(String[] args)
  throws RunnerException, IOException {
    Main.main(args);
}

Agora, precisamos criar um método e anotá-lo com a anotação _ @ Benchmark_ da JMH:

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

Dentro deste método init, precisamos escrever um código que precise ser executado repetidamente para aquecer.

===* 7. Referência de desempenho *

Nos últimos 20 anos, a maioria das contribuições para Java estava relacionada ao GC (Garbage Collector) e JIT (Just In Time Compiler). Quase todos os benchmarks de desempenho encontrados online são feitos em uma JVM já em execução há algum tempo. Contudo,

No entanto, a Beihang University publicou um relatório de benchmark levando em consideração o tempo de aquecimento da JVM. Eles usaram sistemas baseados no Hadoop e Spark para processar dados massivos:

link:/wp-content/uploads/2017/06/jvm.png [imagem:/wp-content/uploads/2017/06/jvm-300x222.png [imagem]]

Aqui, o HotTub designa o ambiente em que a JVM foi aquecida.

Como você pode ver, a velocidade pode ser significativa, especialmente para operações de leitura relativamente pequenas - e é por isso que esses dados são interessantes a considerar.

===* 8. Conclusão*

Neste artigo rápido, mostramos como a JVM carrega classes quando um aplicativo é iniciado e como podemos aquecer a JVM para obter um aumento no desempenho.

Este livro repassa mais informações e diretrizes sobre o tópico, se você quiser continuar.

E, como sempre, o código fonte completo está disponível over no GitHub.