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 de divulgação da Imersão IA da Alura em colaboração com o Google. Mergulhe em Inteligência artificial com a Alura e o Google. Serão cinco aulas gratuitas para você aprender a usar IA na prática e desenvolver habilidades essenciais para o mercado de trabalho. Inscreva-se gratuitamente agora!

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