Compreender o Modelo de Memória da JVM, Gerenciamento de Memória do Java é muito importante se você deseja entender o funcionamento da Coleta de Lixo do Java. Hoje vamos analisar o gerenciamento de memória em Java, diferentes partes da memória da JVM e como monitorar e ajustar a coleta de lixo.
Modelo de Memória do Java (JVM)
Como você pode ver na imagem acima, a memória da 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.
Gerenciamento 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, a coleta de lixo é realizada. Esta 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 de Sobrevivência. 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 sobreviventes.
- 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 sobreviveram após muitos ciclos de GC são movidos para o espaço de memória da geração antiga. Normalmente, isso é feito definindo um limite para a idade dos objetos da geração jovem antes de serem elegíveis para promoção 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 longa duração e sobreviveram após muitas rodadas de GC menor. Normalmente, 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 Stop the World
Todas as Coletas de Lixo são eventos “Parar o Mundo” porque todas as threads da aplicação são interrompidas até que a operação seja concluída. Como a geração Jovem mantém objetos de curta duração, a GC Menor é muito rápida e a aplicação não é afetada por isso. No entanto, a GC Maior leva muito tempo porque verifica todos os objetos vivos. A GC Maior deve ser minimizada porque tornará sua aplicação irresponsiva durante a duração da coleta de lixo. Portanto, se você tem uma aplicação responsiva e há 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. Por isso, é 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 pela JVM para descrever as classes e métodos usados na aplicação. Note que a Perm Gen não faz parte da memória do Heap Java. A Perm Gen é populada pela JVM em tempo de execução com base nas classes usadas pela aplicação. A Perm Gen também contém classes e métodos da biblioteca Java SE. 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 é uma 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 de 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. Elas 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 a partir do método. Você deve ler Diferença entre Memória de Pilha e de Heap.
Gerenciamento de Memória em Java – Interruptores de Memória de Heap Java
O Java fornece muitos interruptores de memória que podemos usar para definir os tamanhos de memória e suas proporções. Alguns dos interruptores de memória comumente usados 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. |
Na maioria das vezes, as opções acima são suficientes, mas se você quiser verificar outras opções também, então por favor confira Página Oficial de Opções da 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 para 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 liberaçã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 objetos que não sã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 em que 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 melhor 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.
- Primeiro, não é eficiente porque a maioria dos objetos recém-criados se tornará não utilizados.
- Em segundo lugar, objetos que estão em uso por vários ciclos de coleta de lixo provavelmente estarão em uso para ciclos futuros também.
As deficiências acima com a abordagem simples são a razão pela qual a Coleta de Lixo em Java é Generacional e temos espaços de Geração Jovem e Geração Antiga na memória heap. Eu já expliquei acima como os objetos são escaneados e movidos de um espaço geracional para outro com base na GC Menor e GC Maior.
Gerenciamento de Memória em Java – Tipos de Coleta de Lixo em Java
Há cinco tipos de estratégias de coleta de lixo que podemos usar em nossas aplicações. Apenas precisamos usar a chave JVM para habilitar a estratégia de coleta de lixo para a aplicação. Vamos analisar cada uma delas uma por uma.
- GC Serial (-XX:+UseSerialGC): O GC Serial usa a abordagem simples de marcação-varredura-compactação para coleta de lixo em gerações jovens e antigas, ou seja, GC Menor e GC Maior. O GC Serial é útil em máquinas cliente, como nossas simples aplicações autônomas e máquinas com CPU menor. É bom para pequenas aplicações com baixa pegada de memória.
- 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 throughput porque 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 (-XX:+UseParallelOldGC): Este é o mesmo que o GC Paralelo, exceto que utiliza várias threads tanto para a coleta de lixo na Geração Jovem quanto na Geração Antiga.
- Coletor de Marcação e Varredura Concorrente (CMS) (-XX:+UseConcMarkSweepGC): O Coletor CMS também é chamado de coletor de baixa pausa concorrente. Ele faz 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 usa 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 de longo prazo é 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á 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, ele primeiro coleta a região com menos dados vivos, daí o nome “Garbage First”. Você pode encontrar mais detalhes sobre isso em Documentação da 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 um aplicativo. Para o meu exemplo, estou usando um dos aplicativos de demonstração fornecidos pelos downloads do Java SE. Se você quiser usar o mesmo aplicativo, vá para a página de Downloads do Java SE e baixe JDK 7 e Demonstrativos e Amostras do JavaFX. O aplicativo de amostra 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 aplicativo Java. O comando usado por mim para iniciar o aplicativo 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 do JVM e as atividades de coleta de lixo. Ele é fornecido com o JDK padrão, então você não precisa fazer mais nada para obtê-lo. Para executar jstat
, você precisa saber o ID do processo do aplicativo, 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
Então, o ID do processo para o meu aplicativo 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 jstat é o intervalo de tempo entre cada saída, então ele imprimirá dados de memória e coleta de lixo a cada 1 segundo. Vamos passar por 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: Essas colunas mostram o tamanho atual e o uso do espaço Eden em KB. Observe que o tamanho do EU está aumentando e assim que ultrapassar o EC, o GC Menor é acionado e o tamanho do EU é reduzido.
- OC e OU: Essas colunas mostram o tamanho atual e o uso atual da geração antiga em KB.
- PC e PU: Essas 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 de GC ocorridos na geração jovem. A coluna YGCT exibe o tempo acumulado para operações de GC para a geração jovem. Observe 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 de GC completos ocorridos. A coluna FGCT exibe o tempo acumulado para operações de GC completas. Observe que o tempo de GC completo é muito alto quando comparado aos tempos de GC da geração jovem.
- GCT: Esta coluna exibe o tempo total acumulado para operações de GC. Observe que é a soma dos valores das colunas YGCT e FGCT.
A vantagem do jstat é que ele pode ser executado em servidores remotos também onde não temos GUI. Observe que a soma de S0C, S1C e EC é 10m como especificado pela opção de JVM -Xmn10m
.
Java VisualVM com Visual GC
Se deseja visualizar operações de memória e GC na interface gráfica, então pode utilizar a ferramenta jvisualvm
. O Java VisualVM também faz parte do JDK, então não é necessário baixá-lo separadamente. Basta executar o comando jvisualvm
no terminal para iniciar a aplicação Java VisualVM. Uma vez iniciada, é necessário instalar o plugin Visual GC na opção Ferramentas -< Plugins, conforme mostrado na imagem abaixo. Após instalar o Visual GC, abra a aplicação na coluna esquerda e vá para a seção Visual GC. Você obterá uma imagem da memória JVM e detalhes da coleta de lixo conforme mostrado na imagem abaixo.
Otimização da Coleta de Lixo em Java
A Afinação da Coleta de Lixo do Java deve ser a última opção que você deve usar para aumentar o rendimento da sua aplicação e apenas quando você observar uma queda de desempenho devido a tempos de GC mais longos causando tempo limite da aplicação. Se você observar erros de java.lang.OutOfMemoryError: Espaço PermGen
nos registros, então tente monitorar e aumentar o espaço de memória Perm Gen usando as opções JVM -XX:PermGen e -XX:MaxPermGen. 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, a afinação da coleta de lixo requer muito esforço e tempo, e não há regra definitiva para isso. Você precisaria experimentar diferentes opções e compará-las para descobrir a melhor adequada para a sua aplicação. Isso é tudo para o Modelo de Memória do Java, Gerenciamento de Memória em Java e Coleta de Lixo. Espero que isso ajude você a entender o processo de memória JVM e coleta de lixo.
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java