Começando com parâmetros e configurações da JVM

Começando com parâmetros e configurações da JVM
lucasas
lucasas

Compartilhe

Quando rodamos nossas aplicações na JVM não sabemos o que acontece internamente dentro dela. Questões como o Garbage Collector, JIT e alocação de memória heap passam desapercebidas por um bom motivo: não devemos nos preocupar (muito) com elas.

Mesmo sem ter um controle direto sobre as diferentes JVMs existentes, muitas vezes precisamos customizar e fazer ajustes finos de seus comportamentos, em especial quando estamos testando a aplicação ou percebemos fraquezas do sistema em produção. Dentre as configurações mais comuns, temos o manjado parâmetro -Xmx para indicar a quantidade máxima de memória que a JVM pode alocar para o heap (ex -Xmx1024m). É curioso saber que essa, assim como todas as que começam com -X, é uma configuração fora do padrão das JVMs, e pode não existir, apesar de que na prática todas a tem: seja a JRockit, a Sun/Oracle, entre outras.

Vamos ver outras importantes configurações e parâmetros importantes para testes e customizações do dia a dia na JVM da Sun/Oracle. Todas as opções relatadas aqui também funcionam na JVM do Mac e na JRockit.

Banner promocional da Alura, com chamada para um evento ao vivo no dia 12 de fevereiro às 18h30, com os dizeres

Desabilitando chamadas explícitas ao Garbage Collector

Alguns desenvolvedores costumam querer intervir na participação do GC e explicitamente fazem invocações a ele: o System.gc();. Invocar o GC manualmente não é uma boa idéia, primeiro porque não é garantido que o mesmo rodará no momento que você o solicitou (por padrão a JVM da Oracle/Sun obedece, mas a JRockit não), segundo porque uma chamada como esta pode fazer com que a JVM faça um "full sweep" da memória heap, ou seja, tudo ficará congelado durante esse tempo ("stop-the-world"), mesmo que isso não fosse necessário.

Não podemos evitar que algum desenvolvedor tente invocar de maneira manual o GC, mas podemos fazer com que a JVM ignore esta invocação. Basta no momento em que formos chamar a JVM, passemos o parâmetro -XX:+DisableExplicitGC. Deste modo a invocação direta é sempre ignorada.

Verificando as chamadas ao GC

Observando como o GC se comporta em nossa aplicação podemos concluir alguns possíveis problemas de performance na mesma. Vamos pegar como exemplo abaixo:

 for (int i = 0; i < 100; i++) { List<Object> lista = new ArrayList<Object>(); for (int j = 0; j < 300000; j++) { lista.add(new Object()); } } 

Habilitando a opção -verbose:gc no momento de rodar este simples código dentro de um main, pode-se notar que o Full GC está sendo executado várias vezes, contrariando a teoria das gerações. Os objetos estão demorando para serem liberados, o que acaba causando várias chamadas ao Full GC para liberar memória. Poderíamos alterar o programa para resolver este problema ou alterar algumas configurações na JVM.

Divisão da memória na JVM da Sun/Oracle não é tão simples

A divisão de memória da JVM da Sun/Oracle não é tão simples assim. Veja mais sobre esta divisão.

Para resolver este problema precisamos entender melhor o funcionamento interno da memória da JVM que divide os objetos de acordo com o seu tempo de vida. Configurando alguns parâmetros na JVM teríamos menos chamadas ao Full GC e com isso os Minors GC serão bem mais performáticos.

Performance

Podemos habilitar a flag -Xprof para obtermos mais informações sobre o processamento da nossa aplicação, chamadas de métodos e em quais objetos estes métodos foram chamados. Esta flag não substitui um profiler profissional como JProfiler ou o TestKit, porém, pode nos trazer informações úteis sobre como encontra-se nossa aplicação durante a sua execução.

Durante a construção de determinados algoritmos surge a necessidade de fazermos invocações recursivas, ou mesmo percorrer muitos métodos. Cada thread possui a sua própria pilha de execução, sendo que esta pilha possui um tamanho máximo (stack size), que dependendo da quantidade de invocações e uso de variáveis locais pode causar o conhecido java.lang.StackOverflowError. Podemos aumentar o tamanho da pilha de execução ajustando a flag -Xss (por exemplo, -Xss1024k).

Mais sobre -verbose

O -verbose pode ser usado além da verificação do comportamento do GC. Problemas de Class Loader Hell podem ser resolvidos se você descobrir de onde a classe que você está utilizando está sendo carregada por algum ClassLoader. Basta adicionar a opção -verbose:class no momento de rodar sua aplicação e será exibido no console qual a localização das classes carregadas.

Outras opções interessantes A memória da JVM possui uma parte conhecida como permanent generation, ou PermGen. Este espaço fica fora do heap e contém objetos internos da JVM, além de objetos do tipo Class, Method, Field e o Pool de Strings.

O erro mais comum referente ao PermGen é o java.lang.OutOfMemoryError: PermGen space, que acaba confundindo o programador. A confusão ocorre porque são partes diferentes de memória, e o desenvolvedor muitas vezes pensa que o erro refere-se a memória heap e acaba tentando corrigí-lo com os parâmetros -Xms e -Xmx.

Para aumentar o tamanho do PermGen devemos utilizar o parâmetro -XX:MaxPermSize (por exemplo, -XX:MaxPermSize=128m). Erros que acontecem e são referentes a esta parte da memória, são difíceis de serem identificados, justamente por não se tratarem de objetos que temos muito controle. Em geral acontecem devido a uma grande quantidade de classes carregadas na memória, e aparece frequentemente no Eclipse quando temos muitos plugins carregados.

Um exemplo real: configurações de JVM no Jetty

Em servlet containers, como o Jetty, existem arquivos onde podemos configurar alguns parâmetros para a JVM, sendo alguns deles sugeridos. Vamos ver algumas das configurações sugeridas/utilizadas por eles:

 # -Xmx2000m - Tamanho máximo da memória heap # -Xmn512m - Tamanho na memória heap para a young generation # -verbose:gc - Observar comportamentos do GC # -XX:+DisableExplicitGC - Desabilitar chamadas explícitas ao GC

\# -XX:+PrintGCDateStamps - Imprime as datas das chamadas ao GC # -XX:+PrintGCTimeStamps- Imprime o timestamp das chamadas ao GC # -XX:+PrintGCDetails - Imprime detalhes das chamadas ao GC # -XX:+PrintCommandLineFlags - Imprime as flags que foram passadas na linha de comando 

Configurações extras do GC

Como se pode ver, grande parte das configurações extras que são frequentemente utilizadas, tem relação com o funcionamento do garbage collector. Devemos nos atentar a detalhes referentes ao GC para melhorar a performance da nossa aplicação.

-XX:+UseParallelGC - Utilizar uma versão do GC que é executada em paralelo no momento de coletar objetos da young generation

-XX:+UseConcMarkSweepGC - Habilitar um GC que coleta os objetos tenured concorrentemente com a execução do sistema. O GC é executado durante um curto período afim de evitar que a aplicação seja parada por muito tempo.

Para habilitar o GC paralelo da young generation com o GC concorrente inicia o JVM com a flag -XX:+UseParNewGC. Imporante! Não use a flag -XX:+UseParallelGC juntamente com esta flag. Mais detalhes sobre melhorias no GC podem ser encontradas neste artigo.

Conclusão

Obviamente estas flags não podem ser usadas sem critério, mas conhecê-las podem nos ajudar a melhorarmos como desenvolvedores Java. Existem realmente muitas opções da JVM, e frequentemente ajustes finos podem mudar bruscamente a performance e escalabilidade da sua aplicação.

Veja outros artigos sobre Programação