Projeto OpenJDK CRaC: reduzindo drasticamente o tempo de inicialização de aplicativos Java
Um pouco de história
O objetivo do Java sempre foi ser independente de plataforma. Isto é, sem precisar compilar o código novamente, é possível rodar em qualquer dispositivo compatível com a máquina virtual (JVM). O famoso slogam (write once, run anywhere)
Desafios da inicialização de aplicativos na JVM
Embora o Java seja uma linguagem popular, a inicialização de aplicativos na JVM pode ser um processo demorado. Isso ocorre porque a JVM precisa realizar várias etapas antes que um aplicativo possa ser executado. Primeiro, a JVM precisa carregar e inicializar todas as classes necessárias para o aplicativo. Isso envolve ler os arquivos de classe, desempacotar arquivos JAR e inicializar todas as dependências.
Em seguida, a JVM precisa fazer algumas otimizações de tempo de execução, como compilação just-in-time (JIT) e inlining de métodos. Essas otimizações ajudam a melhorar o desempenho do aplicativo, mas levam tempo para serem concluídas. Por fim, a JVM precisa aquecer o aplicativo, o que envolve analisar o código e determinar quais partes do aplicativo devem ser otimizadas.
Esse processo pode levar muito tempo e afetar a velocidade de inicialização. Esses desafios de inicialização são especialmente problemáticos para aplicativos que precisam ser iniciados rapidamente ou executados em ambientes de contêiner, onde várias JVMs podem competir por recursos limitados. Para cada réplica do aplicativo, o mesmo processo será repetido.
Fonte: https://pt.slideshare.net/SimonRitter/java-on-crac
O mecanismo de execução
Depois que o bytecode é carregado na memória, ele é processado pelo mecanismo de execução da JVM. O mecanismo de execução consiste em vários componentes, incluindo o interpretador, o compilador C1 JIT e o compilador C2 JIT. O interpretador pega o bytecode linha por linha e o interpreta em instruções da CPU. A JVM observa o código e traça o perfil de sua execução, contando com que frequência os métodos são chamados. Se um método for chamado com frequência, ele será colocado no compilador C1 JIT, que o compila rapidamente, sem muitas otimizações, para reduzir o tempo de inicialização. A JVM continua a criar o perfil do código e, se um método for chamado com ainda mais frequência, ele será colocado no compilador C2 JIT, que pode produzir código altamente otimizado e de alto desempenho. Essa combinação de interpretadores e compiladores JIT permite que a JVM otimize o código à medida que ele é executado, resultando em uma execução mais rápida.
OpenJDK CRaC: uma solução para a inicialização rápida de aplicativos na JVM
O Coordinated Restore and Checkpoint, ou CRaC, é uma solução que visa melhorar o tempo de inicialização da JVM. O CRaC permite pausar e restaurar um aplicativo baseado em JVM, eliminando a necessidade de interpretação, compilação e otimização durante a inicialização. Em vez disso, você pode criar um ponto de verificação do seu aplicativo em seu estado totalmente otimizado e começar a partir desse ponto, reduzindo significativamente o tempo que leva para o seu aplicativo atingir o desempenho total.
Essa solução foi desenvolvida para permitir que aplicativos na JVM sejam iniciados mais rapidamente, reduzindo o tempo necessário para carregar e inicializar classes, bem como para realizar otimizações JIT.
Como funciona
O CRaC é baseado em um recurso do Linux chamado CRIU, um projeto para implementar funcionalidade de checkpoint/restore para Linux.
Checkpoint/Restore In Userspace, ou CRIU é um software Linux criado em 2013. Ele pode congelar um contêiner em execução (ou um aplicativo individual) e verificar seu estado no disco. Os dados salvos podem ser usados para restaurar o aplicativo e executá-lo exatamente como estava durante o congelamento.
CRaC só funciona no sistema operacional Linux no momento porque a implementação do OpenJDK depende deste recurso
O CRaC exige que todos os arquivos e conexões de rede sejam fechados antes de tirar um snapshot e após restaurá-lo. É por isso que o CRaC requer suporte no tempo de execução Java e no framework. A API CRaC também permite que aplicativos Java atuem antes de um snapshot ser obtido e depois de restaurado. Os aplicativos podem obter um snapshot por meio de uma chamada de API CRaC ou com o utilitário Java jcmd. Um OpenJDK com suporte ao CRaC carrega um snapshot com a opção de linha de comando -XX:CRaCRestoreFrom.
O CRaC funciona tirando uma foto (snapshot) do estado do seu aplicativo, incluindo a memória JVM e os recursos que ele está usando, e armazenando-o em arquivos. Essa foto, ou ponto de verificação, pode então ser usada para restaurar seu aplicativo ao estado anterior. Ao restaurar a partir de um ponto de verificação, a JVM é essencialmente iniciada de onde parou, ignorando as fases de interpretação, compilação e otimização. Isso permite que seu aplicativo atinja desempenho total quase instantaneamente.
Fonte: https://pt.slideshare.net/SimonRitter/java-on-crac
Distribuições da OpenJDK com CRaC
O projeto CRaC foi aceito no OpenJDK em agosto de 2021, o que significa sua importância para o futuro do Java. Porém ainda está longe de ser incluído no projeto principal.
Algumas distribuições JDK, incluindo Azul Zulu JDK e BellSoft Liberica JDK já oferecem suporte CRaC integrado. Frameworks como Micronaut, Quarkus e Spring já integraram suporte CRaC.
Utilização de outras linguagens na JVM
O OpenJDK CRaC também permite que aplicativos escritos em outras linguagens, como Kotlin, sejam executados na JVM com a mesma rapidez e eficiência.
Benefícios
Os benchmarks mostram que o CRaC pode reduzir os tempos de inicialização em 90% ou mais, levando a tempos de resposta de serviço mais rápidos e melhor desempenho.
Os aplicativos atingem o desempenho máximo muito mais rapidamente após serem restaurados, melhorando ainda mais a capacidade de resposta resultando em melhor utilização de recursos: Menos CPU e memória são usadas durante as inicializações, liberando recursos para outras tarefas.
Existem vários benefícios em usar o CRaC para melhorar o tempo de inicialização da JVM:
- Tempo rápido para desempenho total: ao iniciar seu aplicativo a partir de um ponto de verificação, você pode obter desempenho total quase instantaneamente, sem esperar pela interpretação, compilação e otimização.
- Eliminação de sobrecarga de interpretação e compilação: com o CRaC, você pode ignorar as fases de interpretação e compilação durante a inicialização, reduzindo o tempo que leva para seu aplicativo ficar totalmente otimizado.
- Capacidade de resposta aprimorada: ao reduzir o tempo de inicialização da JVM, seu aplicativo pode responder mais rapidamente, proporcionando uma melhor experiência ao usuário.
- Economia de custos: com tempos de inicialização mais rápidos, você pode reduzir o número de nós de alta potência necessários em sua infraestrutura de nuvem, resultando em economia de custos.
Comparativo de aplicações iniciadas sem o CRaC e com o CRaC
Fonte: https://pt.slideshare.net/SimonRitter/java-on-crac
Na prática
Nesta prova de conceito, temos uma aplicação Spring Boot 3.2 rodando com a Azul Zulu JDK 21. E para que possamos testar em Sistemas Operacionais como Mac e Windows, vamos utilizar o Docker para simular um ambiente Linux.
O CRaC exige que todos os arquivos e conexões de rede sejam fechados antes de tirar um snapshot e reabri-lo após restaurá-lo. É por isso que o CRaC requer suporte no tempo de execução Java e no framework. A API CRaC também permite que os aplicativos Java atuem antes de um snapshot ser obtido e depois de restaurado. Os aplicativos podem obter um snapshot por meio de uma chamada de API CRaC ou com o utilitário Java jcmd. O Azul OpenJDK carrega um snapshot com a opção de linha de comando -XX:CRaCRestoreFrom.
Para inicializar a aplicação, é necessário informar o parametro -XX:CRaCCheckpointTo=$PATH onde o $PATH é o local no disco onde a JDK irá utilizar para gravar os arquivos para posteriormente restaurar. Ex:
java -XX:CRaCCheckpointTo=cr -jar demo.jar
Com a aplicação em execução, basta obter um snapshot através do comando:
jcmd demo.jar JDK.checkpoint
Para fazer isso, abra um novo terminal, entre no container e execute o checkpoint:
- Entrando no container em execução: docker exec -it demo_crac /bin/bash
- Obtendo o checkpoint: jcmd demo.jar JDK.checkpoint
Neste momento serão gerados vários arquivos no local indicado na variável $PATH. A aplicação será interrompida após um snapshot ser executado com sucesso.
Para restaurar a aplicação a partir de um snapshot, execute o seguinte comando: java -XX:CRaCRestoreFrom=$PATH.
Ex: java -XX:CRaCRestoreFrom=cr
Conclusão:
CRaC apresenta uma abordagem inovadora para superar gargalos de inicialização de JVM, oferecendo lançamentos mais rápidos sem sacrificar o desempenho. Sua adequação depende das características específicas da aplicação e de considerações técnicas. Os desenvolvedores interessados em otimizar os tempos de inicialização de microsserviços devem acompanhar de perto o desenvolvimento do CRaC e avaliar seu potencial para suas necessidades específicas.
Comentários
Postar um comentário