Compreender o Modelo de Memória JVM, a Gestão de Memória Java é muito importante se você deseja entender o funcionamento da Coleta de Lixo Java. Hoje vamos analisar a gestão de memória em Java, as diferentes partes da memória JVM e como monitorar e ajustar a coleta de lixo.
Java (JVM) Memory Model
Como você pode ver na imagem acima, a memória JVM é dividida em partes separadas. Em um nível amplo, a memória do Heap da JVM é fisicamente dividida em duas partes – Geração Jovem e Geração Antiga.
Gestão de Memória em Java – Geração Jovem
A geração jovem é o local onde todos os novos objetos são criados. Quando a geração jovem é preenchida, é realizada uma coleta de lixo. Essa coleta de lixo é chamada de GC Menor. A Geração Jovem é dividida em três partes – Memória Eden e dois espaços de Memória Sobrevivente. Pontos importantes sobre os Espaços da Geração Jovem:
- A maioria dos objetos recém-criados está localizada no espaço de memória Eden.
- Quando o espaço Eden é preenchido com objetos, é realizada uma GC Menor e todos os objetos sobreviventes são movidos para um dos espaços de sobrevivente.
- A GC Menor também verifica os objetos sobreviventes e os move para o outro espaço de sobrevivente. Assim, em um determinado momento, um dos espaços de sobrevivente está sempre vazio.
- Objetos que sobrevivem após muitos ciclos de GC são movidos para o espaço de memória da Geração Antiga. Geralmente, isso é feito definindo um limite para a idade dos objetos da geração jovem antes que se tornem elegíveis para serem promovidos para a Geração Antiga.
Gerenciamento de Memória em Java – Geração Antiga
A memória da Geração Antiga contém os objetos que têm uma longa vida útil e sobreviveram após muitas rodadas de GC Menor. Geralmente, a coleta de lixo é realizada na memória da Geração Antiga quando está cheia. A Coleta de Lixo da Geração Antiga é chamada de GC Maior e geralmente leva mais tempo.
Evento de Parada do Mundo
Todas as Coletas de Lixo são eventos “Stop the World” porque todas as threads de aplicação são interrompidas até que a operação seja concluída. Como a geração Young mantém objetos de curta duração, a Coleta de Lixo Menor é muito rápida e a aplicação não é afetada por isso. No entanto, a Coleta de Lixo Maior leva muito tempo porque verifica todos os objetos vivos. A Coleta de Lixo Maior deve ser minimizada porque tornará sua aplicação não responsiva durante a duração da coleta de lixo. Portanto, se você tiver uma aplicação responsiva e houver muitas Coletas de Lixo Maiores acontecendo, você notará erros de timeout. A duração da coleta de lixo depende da estratégia usada para a coleta de lixo. Portanto, é necessário monitorar e ajustar o coletor de lixo para evitar timeouts em aplicações altamente responsivas.
Modelo de Memória Java – Geração Permanente
A Geração Permanente ou “Perm Gen” contém os metadados da aplicação necessários pelo JVM para descrever as classes e métodos usados na aplicação. Note que Perm Gen não faz parte da memória do Heap Java. Perm Gen é populada pelo JVM em tempo de execução com base nas classes usadas pela aplicação. Perm Gen também contém classes e métodos da biblioteca SE do Java. Os objetos Perm Gen são coletados pelo coletor de lixo em uma coleta de lixo completa.
Modelo de Memória Java – Área de Método
A Área de Método faz parte do espaço na Geração Perm e é usada para armazenar a estrutura da classe (constantes em tempo de execução e variáveis estáticas) e o código para métodos e construtores.
Modelo de Memória Java – Pool de Memória
Os Pools de Memória são criados pelos gerenciadores de memória da JVM para criar um pool de objetos imutáveis se a implementação suportar. O Pool de Strings é um bom exemplo desse tipo de pool de memória. O Pool de Memória pode pertencer ao Heap ou à Geração Perm, dependendo da implementação do gerenciador de memória da JVM.
Modelo de Memória Java – Pool de Constantes em Tempo de Execução
O pool de constantes em tempo de execução é a representação em tempo de execução por classe do pool de constantes em uma classe. Ele contém constantes em tempo de execução da classe e métodos estáticos. O pool de constantes em tempo de execução faz parte da área de método.
Modelo de Memória Java – Memória de Pilha Java
A memória de pilha Java é usada para a execução de uma thread. Ela contém valores específicos do método que são de curta duração e referências a outros objetos no heap que estão sendo referenciados pelo método. Você deve ler Diferença entre Memória de Pilha e Heap.
Gerenciamento de Memória em Java – Trocas de Memória de Heap Java
O Java fornece muitas trocas de memória que podemos usar para definir os tamanhos de memória e suas proporções. Algumas das trocas de memória comumente usadas são:
VM Switch | VM Switch Description |
---|---|
-Xms | For setting the initial heap size when JVM starts |
-Xmx | For setting the maximum heap size. |
-Xmn | For setting the size of the Young Generation, rest of the space goes for Old Generation. |
-XX:PermGen | For setting the initial size of the Permanent Generation memory |
-XX:MaxPermGen | For setting the maximum size of Perm Gen |
-XX:SurvivorRatio | For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8. |
-XX:NewRatio | For providing ratio of old/new generation sizes. The default value is 2. |
Muitas vezes, as opções acima são suficientes, mas se você quiser verificar outras opções também, por favor, consulte Página Oficial de Opções JVM.
Gerenciamento de Memória em Java – Coleta de Lixo em Java
A coleta de lixo em Java é o processo de identificar e remover os objetos não utilizados da memória e liberar espaço para ser alocado a objetos criados em processamentos futuros. Uma das melhores características da linguagem de programação Java é a coleta de lixo automática, ao contrário de outras linguagens de programação como C, onde a alocação e desalocação de memória são processos manuais. O Coletor de Lixo é o programa em execução em segundo plano que examina todos os objetos na memória e identifica os objetos que não estão referenciados por nenhuma parte do programa. Todos esses objetos não referenciados são excluídos e o espaço é recuperado para alocação a outros objetos. Uma das formas básicas de coleta de lixo envolve três etapas:
- Marcação: Esta é a primeira etapa onde o coletor de lixo identifica quais objetos estão em uso e quais não estão em uso.
- Exclusão Normal: O Coletor de Lixo remove os objetos não utilizados e recupera o espaço livre para ser alocado a outros objetos.
- Exclusão com Compactação: Para melhorar o desempenho, após excluir objetos não utilizados, todos os objetos sobreviventes podem ser movidos para ficarem juntos. Isso aumentará o desempenho da alocação de memória para novos objetos.
Há dois problemas com uma abordagem simples de marcação e exclusão.
- Isso não é eficiente porque a maioria dos objetos recém-criados tenderá a ficar inutilizados.
- Os objetos que estão em uso em vários ciclos de coleta de lixo provavelmente estarão em uso em ciclos futuros também.
As deficiências acima do aproximado simples é a razão pela qual a coleta de lixo do Java é geracional e nós temos Geração Jovem e Geração Antiga espaços na memória do heap. Já expliquei acima como os objetos são examinados e movidos de um espaço geracional para outro com base no GC Menor e GC Maior.
Gerenciamento de Memória no Java – Tipos de Coleta de Lixo do Java
Existem cinco tipos de coleta de lixo que podemos usar em nossas aplicações. Nós apenas precisamos usar o interruptor JVM para habilitar a estratégia de coleta de lixo para a aplicação. Vamos dar uma olhada em cada um deles.
- GC Serial (-XX:+UseSerialGC): O GC Serial usa o aproximado marcar-varrer-compactar abordagem para coleta de lixo de gerações jovem e antiga, ou seja, GC Menor e GC Maior. O GC Serial é útil em máquinas cliente, como nossas aplicações autônomas simples e máquinas com CPU menor. É bom para pequenas aplicações com baixa pegada de memória.
- Coletor Paralelo (GC Paralelo -XX:+UseParallelGC): O GC Paralelo é o mesmo que o GC Serial, exceto que ele cria N threads para a coleta de lixo na geração jovem, onde N é o número de núcleos de CPU no sistema. Podemos controlar o número de threads usando a opção
-XX:ParallelGCThreads=n
da JVM. O Coletor de Lixo Paralelo também é chamado de coletor de rendimento, pois utiliza várias CPUs para acelerar o desempenho do GC. O GC Paralelo usa uma única thread para a coleta de lixo na Geração Antiga. - GC Paralelo Antigo (Parallel Old GC -XX:+UseParallelOldGC): Este é o mesmo que o GC Paralelo, exceto que ele utiliza várias threads tanto para a Geração Jovem quanto para a Geração Antiga da coleta de lixo.
- Coletor de Marcação Concorrente (CMS) (-XX:+UseConcMarkSweepGC): O Coletor CMS também é conhecido como coletor concorrente de baixa pausa. Ele realiza a coleta de lixo para a Geração Antiga. O coletor CMS tenta minimizar as pausas devido à coleta de lixo fazendo a maior parte do trabalho de coleta de lixo de forma concorrente com as threads da aplicação. O coletor CMS na geração jovem utiliza o mesmo algoritmo que o coletor paralelo. Este coletor de lixo é adequado para aplicações responsivas onde não podemos tolerar tempos de pausa mais longos. Podemos limitar o número de threads no coletor CMS usando a opção
-XX:ParallelCMSThreads=n
da JVM. - Coletor de Lixo G1 (-XX:+UseG1GC): O coletor de lixo Garbage First, ou G1, está disponível a partir do Java 7 e seu objetivo principal é substituir o coletor CMS. O coletor G1 é um coletor de lixo paralelo, concorrente e compactador incremental com baixa pausa. O Coletor Garbage First não funciona como outros coletores e não há o conceito de espaço de geração Jovem e Velha. Ele divide o espaço de heap em várias regiões de heap de tamanho igual. Quando uma coleta de lixo é invocada, primeiro coleta a região com menos dados vivos, daí o “Garbage First”. Você pode encontrar mais detalhes sobre ele em Documentação do Oracle sobre o Coletor Garbage-First.
Gerenciamento de Memória em Java – Monitoramento da Coleta de Lixo em Java
Podemos usar a linha de comando do Java, bem como ferramentas de interface de usuário para monitorar as atividades de coleta de lixo de uma aplicação. Para o meu exemplo, estou utilizando uma das aplicações de demonstração fornecidas pelos downloads do Java SE. Se você quiser usar a mesma aplicação, vá para a página de Downloads do Java SE e baixe JDK 7 e Demos e Exemplos do JavaFX. A aplicação de exemplo que estou usando é Java2Demo.jar e está presente no diretório jdk1.7.0_55/demo/jfc/Java2D
. No entanto, este é um passo opcional e você pode executar os comandos de monitoramento de GC para qualquer aplicação Java. O comando usado por mim para iniciar a aplicação de demonstração é:
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
Podemos usar a ferramenta de linha de comando jstat
para monitorar a memória JVM e as atividades de coleta de lixo. Ela vem com o JDK padrão, então você não precisa fazer mais nada para obtê-la. Para executar o jstat
, você precisa saber o ID do processo da aplicação, que pode ser obtido facilmente usando o comando ps -eaf | grep java
.
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
Portanto, o ID do processo para a minha aplicação Java é 9582. Agora podemos executar o comando jstat conforme mostrado abaixo.
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
O último argumento para o jstat é o intervalo de tempo entre cada saída, então ele imprimirá os dados de memória e coleta de lixo a cada 1 segundo. Vamos analisar cada uma das colunas uma por uma.
- S0C e S1C: Esta coluna mostra o tamanho atual das áreas Survivor0 e Survivor1 em KB.
- S0U e S1U: Esta coluna mostra o uso atual das áreas Survivor0 e Survivor1 em KB. Observe que uma das áreas de sobrevivência está vazia o tempo todo.
- EC e EU: Estas colunas mostram o tamanho atual e o uso do espaço Eden em KB. Note que o tamanho do EU está aumentando e assim que ultrapassar o EC, será chamado o GC Menor e o tamanho do EU será reduzido.
- OC e OU: Estas colunas mostram o tamanho atual e o uso atual da geração antiga em KB.
- PC e PU: Estas colunas mostram o tamanho atual e o uso atual da Perm Gen em KB.
- YGC e YGCT: A coluna YGC exibe o número de eventos GC ocorridos na geração jovem. A coluna YGCT exibe o tempo acumulado para operações GC na geração jovem. Note que ambos são aumentados na mesma linha em que o valor do EU é reduzido devido ao GC menor.
- FGC e FGCT: A coluna FGC exibe o número de eventos Full GC ocorridos. A coluna FGCT exibe o tempo acumulado para operações Full GC. Note que o tempo de Full GC é muito alto quando comparado aos tempos de GC da geração jovem.
- GCT: Esta coluna exibe o tempo total acumulado para operações GC. Note que é a soma dos valores das colunas YGCT e FGCT.
A vantagem do jstat é que ele pode ser executado também em servidores remotos onde não temos GUI. Note que a soma de S0C, S1C e EC é 10m conforme especificado através da opção JVM -Xmn10m
.
Java VisualVM com Visual GC
Se você deseja ver as operações de memória e GC na GUI, então você pode usar a ferramenta jvisualvm
. Java VisualVM também faz parte do JDK, então você não precisa baixá-lo separadamente. Basta executar o comando jvisualvm
no terminal para iniciar o aplicativo Java VisualVM. Uma vez iniciado, você precisa instalar o plugin Visual GC na opção Ferramentas -< Plugins, como mostrado na imagem abaixo. Após instalar o Visual GC, basta abrir o aplicativo no painel esquerdo e ir para a seção Visual GC. Você obterá uma imagem da memória do JVM e detalhes da coleta de lixo conforme mostrado na imagem abaixo.
Ajuste de Coleta de Lixo em Java
Ajuste de Coleta de Lixo em Java deve ser a última opção que você deve usar para aumentar o throughput de sua aplicação e apenas quando você perceber uma queda no desempenho devido a tempos de GC mais longos que causam timeout da aplicação. Se você ver erros java.lang.OutOfMemoryError: espaço PermGen
nos logs, então tente monitorar e aumentar o espaço de memória Perm Gen usando as opções -XX:PermGen e -XX:MaxPermGen do JVM. Você também pode tentar usar -XX:+CMSClassUnloadingEnabled
e verificar como está se saindo com o coletor de lixo CMS. Se você observar muitas operações de GC completo, então você deve tentar aumentar o espaço de memória da geração antiga. No geral, o ajuste de coleta de lixo exige muito esforço e tempo e não há uma regra definitiva para isso. Você precisaria tentar diferentes opções e compará-las para descobrir a melhor adequada para sua aplicação. Isso é tudo para o Modelo de Memória Java, Gerenciamento de Memória em Java e Coleta de Lixo, espero que ajude você a entender a memória JVM e o processo de coleta de lixo.
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java