Gerenciamento de memória em Java
O GlassFish é um servidor de aplicações. Por ser escrito em Java, está sujeito às regras de gerenciamento de memória da Máquina Virtual Java (JVM – Java Virtual Machine).
A JVM possui um mecanismo próprio para gerenciamento de memória chamado Garbage Collector (Coletor de lixo) ou simplesmente: GC. Todos os objetos criados durante a execução de um programa na JVM estão sujeitos aos comportamentos definidos para o Coletor de Lixo. Estes comportamentos (que podem ser configurados via parâmetros – veremos mais adiante) definem a quantidade de memória disponível, o algoritmo de limpeza que o coletor vai utilizar, entre outros.
Antes de apresentar estes parâmetros, vamos ver como funciona a alocação de memória na JVM.
Alocação de memória da JVM
A memória da JVM é dividida em várias partes, conforme a figura abaixo:
Eden: É a área de memória reservada para a alocação dos novos objetos. Cada vez que um novo objeto é criado (por exemplo: List usuarios = new ArrayList()), ele é alocado nesta área reservada para o Eden. Esta área tende a ser rapidamente ocupada, pois durante a execução de um programa são alocados muitos objetos.
S1 e S2: Ou Survivor1 e Survivor2. Quando o Eden está totalmente ocupado, a JVM executa um Garbage Collector para eliminar os objetos que não estão em uso e liberar memória. Os objetos que estavam no Eden e sobreviveram ao GC (ou seja: ainda estavam em uso no momento do GC) são movidos para esta área.
Old Generation: Quando a área reservada para o Survivor está totalmente alocada, é executado um GC na mesma. Os objetos que ainda estiverem em uso vão para o Old Generation.
Resumindo, o ciclo de vida de um objeto em uso é o seguinte: É alocado no Eden, quando o Eden estiver totalmente cheio, o objeto vai para o Survivor. Quando o Survivor estiver totalmente alocado, o objeto vai para o Old Generation. Caso o objeto deixe de ser utilizado durante este ciclo, ele será liberado quando o GC executar.
PermGen: É a área destinada pela JVM para armazenar as definições das classes. Enquanto as outras áreas salvam os dados do programa em execução, o PermGen salva as definições das interfaces dos objetos. Em uma analogia ao banco de dados, nesta área é como se estivessem salvas as definições das tabelas, triggers, índices, etc.. enquanto que nas demais áreas ficam salvos os conteúdos (dados) das tabelas.
Parâmetros para configuração da memória
Os tamanhos de cada uma das áreas acima podem ser configurados via parâmetros, informados via linha de comando para a JVM.
- Xms: Define a quantidade inicial de memória que o programa irá alocar para armazenamento de dados (Eden + S1 + S2 + OldGeneration). Por exemplo, para iniciar o programa com 512MB RAM: -Xms512m
-Xmx: Define a quantidade máxima de memória que o programa irá alocar para armazenamento de dados (Eden + S1 + S2 + OldGeneration). Por exemplo, para informar à JVM para alocar no máximo 1GB de RAM: -Xmx1024m ou –Xmx1g (o parâmetro aceita a sintaxe tanto em MB quanto GB).
Atenção: para uma boa performance, é sempre bom configurar os dois parâmetros acima com o mesmo valor. Desta forma, o Sistema Operacional já irá alocar uma área fixa de memória para toda a JVM e não vai mais precisar fazer realocações durante a execução.
-Xmn: Define a quantidade de memória reservada para o Eden. Este espaço é uma parte do alocado pelos parâmetros acima, conforme ilustrado na figura. Por exemplo, para alocar 256 MB RAM para o Eden: -Xmn256m
-XX:SurvivorRatio: Define a relação de tamanho entre as duas áreas de Survivor e o Eden. Por exemplo, para definir que cada área de Survivor tenha 1/10 do tamanho do Eden, o parâmetro fica assim: -XX:SurvivorRatio=10.
O espaço ocupado pelo Old Generation é o que sobrou do espaço alocado pelos parâmetros acima.
-XX:MaxPermSize: Define o tamanho da área destinada para o metadata (definições) das classes utilizadas.
Do ponto de vista do Sistema Operacional, a memória utilizada pelo processo java.exe é a soma do Xmx e do MaxPermSize. Atenção: Se você estiver utilizando uma JVM de 32 bits, a soma destes valores não pode ser superior a 2GB, que é o máximo possível em um aplicativo 32 bits.
Exemplo de configuração: -Xmx2g –Xms2g –Xmn512m –XX:SurvivorRatio=10 –XX:MaxPermSize=384m
Com os parâmetros acima, a memória vai ficar assim dividida:
- Eden: 512MB
- Survidor1: 51,2 MB (10% do Eden)
- Survivor2: 51,2 MB (10% do Eden)
- OldGeneration: 1385,6 MB (É a diferença entre o total alocado (2GB) e os já utilizados pelo Eden + Survivors)
- PermGen: 384MB
O Garbage Collector
O Garbage Collector é um mecanismo da JVM que gerência os objetos alocados na memória. Ele pode ser configurado para trabalhar de várias formas, otimizando o consumo de recursos e a performance.
Sempre deve-se procurar minimizar os momentos de stop-the-world (ou Full GC) do GC. Estes momentos ocorrem quando o GC precisa parar todos os processos (a JVM parece que fica congelada mesmo) para efetuar a limpeza da memória. Normalmente isto ocorre na limpeza do Old Generation.
Uma informação que você sempre tem que ter em mente é a seguinte: Quanto mais à direita (na figura acima) um objeto estiver, mais caro é para liberá-lo. Um objeto é facilmente liberado enquanto está no Eden, mas se ele estiver no Old Generation, será mais demorado para liberá-lo. Então todos os parâmetros passados para o GC são para otimizar a limpeza já no Eden, para que poucos objetos sejam enviados para os Survivors e depois para o Old Generation. Assim o usuário não perceberá a execução do GC, resultando em uma melhor performance.
Os parâmetros informados abaixo foram obtidos através de testes realizados e se mostraram os mais eficazes em termos de tempo de resposta para o usuário. Eles visam diminuir ao máximo as paradas de resposta da JVM para execução do GC. São eles:
-XX:+UseConcMarkSweepGC: Indica para o GC utilizar um algoritmo que trabalha de forma concorrente e tem uma boa performance na limpeza do Old Generation. Mesmo assim, se a área do Old Generation estiver muito cheia, pode demorar um tempo para efetuar a limpeza. Este algoritmo trabalha em duas fases: primeiro varre toda a área do Old Generation marcando os objetos que podem ser excluídos e depois passa novamente em toda a área fazendo efetivamente a exclusão dos mesmos.
-XX:+CMSParallelRemarkEnabled: É uma otimização para que o algoritmo acima trabalhe de forma paralela em algumas etapas, diminuindo o tempo de processamento
-XX:+UseParNewGC: Indica para utilizar um algoritmo que trabalha de forma paralela (várias threads podem ser utilizadas) para limpeza do Eden. A idéia é fazer com algumas threads fiquem constantemente limpando o Eden, para já liberar o espaço dos objetos que podem ser excluídos. Assim evita-se a promoção” dos objetos para o Survivor e depois para o Old Generation.
-server: Indica para a JVM que o programa que está em execução tem um comportamento mais próximo de um servidor do que um cliente (um programa com interface em Java Swing, que roda em uma estação é um exemplo de programa cliente). Este parâmetro indica que o programa deve ficar executando direto por vários dias. Com este parâmetro, a JVM faz algumas otimizações no gerenciamento da memória.
Com os parâmetros acima, o comportamento do GC é o seguinte: fica efetuando pequenas e constantes limpezas da memória, imperceptíveis para o usuário e praticamente não consumindo processamento.
Existem vários outros parâmetros para configuração do GC. Mas sempre recomenda-se que eles somente sejam informados caso for necessário. Com os parâmetros acima, a JVM já consegue um bom desempenho.
Configuração padrão | Configuração recomendada | Descrição |
---|---|---|
-XX:MaxPermSize=192m | -XX:MaxPermSize=384m | O valor desta chave deve ser alterado. |
-client | -server | O valor desta chave deve ser alterado. |
-Xmx512m | -Xmx1200m | O valor desta chave deve ser alterado. |
. | -Xms1200m | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -Xmn512m | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -XX:+UseConcMarkSweepGC | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -XX:+UseParNewGC | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -XX:SurvivorRatio=20 | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -XX:+CMSParallelRemarkEnabled | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
. | -Xrs | Por padrão esta chave está suprimida na configuração e deve ser incluída. |
-XX:NewRatio=2 | . | Esta chave deve ser removida da configuração. |