Pular para o conteúdo principal

Introdução ao projeto OpenJDK CRaC


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 código fonte da aplicação está disponível em https://github.com/sandrogiacom/crac-demo

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:

  1. Entrando no container em execução: docker exec -it demo_crac /bin/bash
  2. 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



Aplicação inicializada a partir de um snapshot em 59 milisegundos


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

Postagens mais visitadas deste blog

Java Records

  Java Records Imutável, Simples e limpa Esta funcionalidade da linguagem apareceu pela primeira vez na versão 14 como experimental e assim continuou até a versão 15 . Agora liberada de forma definitiva no Java 16 . O objetivo é ser possível ter classes que atuam como portadores transparentes de dados imutáveis. Os registros podem ser considerados tuplas nominais. Ou seja, após criado, um record não pode mais ser alterado. Records oferece uma uma sintaxe compacta para declarar classes que são portadores transparentes para dados imutáveis superficiais visando reduzir significamente o detalhamento dessas classes e irá melhorar a capacidade de leitura e manutenção do código. Vamos seguir um exemplo de uma classe chamada Pessoa . O primeiro exemplo vamos utilizar o modo tradicional. public class Pessoa { private String nome; private int idade; public Pessoa (String nome, int idade) { super (); this .nome = nome; this .idade = idade; } public String getNo

Java 8 ao 18: Mudanças mais importantes na plataforma Java

    Vamos rever muitas das mudanças mais importantes na plataforma Java que aconteceram entre a versão 8 (2014) e 18 (2022)   O Java 8 foi lançado em março de 2014 e o Java 18 em março de 2022. São 8 anos de progresso, 203 JEPs (JDK Enhancement Proposals ), entre essas duas versões. Neste post, revisaremos as mudanças mais importantes e discutiremos os benefícios e desafios da adoção de versões mais recentes do JDK para novos aplicativos e para os mais antigos compilados com versões mais antigas. Desde a versão 9, o Java tem novos recursos a cada 6 meses e é muito difícil acompanhar essas novas mudanças. A maioria das informações na internet descreve as mudanças entre as duas últimas versões do Java. No entanto, se você estiver em uma situação semelhante à minha, não está usando uma das versões mais recentes do Java, mas uma das várias versões anteriores (Geralmente 8 ou 11 que são as versões de suporte estendido). Então é útil saber quais novos recursos foram adicionados d

O suporte de longo prazo e o que o LTS significa para o ecossistema Java

A arte do suporte de longo prazo e o que o LTS significa para o ecossistema Java Aqui está o que o Java 17 tem em comum com o Java 11 e o Java 8. Em junho de 2018, há pouco mais de três anos, a Oracle e outros participantes do ecossistema Java anunciaram uma mudança no modelo de cadência de lançamento para Java SE. Em vez de ter um lançamento principal planejado a cada dois ou quatro anos (que geralmente se torna de três a quatro anos), um novo modelo de lançamento de recursos de seis meses seria usado: a cada três anos, um lançamento seria designado como Long-Term Support (LTS) e receba apenas atualizações trimestrais de segurança, estabilidade e desempenho. Esse padrão foi emprestado descaradamente do modelo de lançamento do Mozilla Firefox, mas o ajustou para ficar mais alinhado com os requisitos de uma plataforma de desenvolvimento. A primeira versão do Java lançada sob esse modelo foi o Java SE 11. O lançamento do Java SE 17, o segundo lançamento do LTS sob o novo