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

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.

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.

12 Comentários

  1. Rodrigo Gomes 09/09/2010 at 18:40 #

    Olás, legal o post!

    O chato é que a JVM da IBM (que vem no Websphere) não implementa (quase) nenhum comando de GC desses (e nem é obrigada como vc salientou). Pelo menos com esses nomes…o funcionamento é parecido mas ela colocou outros nomes rs

  2. Bruno Laturner 09/09/2010 at 21:10 #

    Tem uma apresentação muito boa de GC Tuning que aconteceu na FISL10, recomendo uma olhada:

    http://www.scribd.com/doc/37127094/GCTuningPresentationFISL10

    E sobre a parte de chamar o System.gc() não ser uma boa idéia, às vezes é necessário em situações muito estranhas.

    Uma que lembro é tentar um file.delete() após fazer umas leituras no arquivo e a operação não acontecer. Só depois de uma chamada manual à GC que o arquivo é apagado de vez.

  3. Paulo Silveira 09/09/2010 at 21:27 #

    Oi Bruno! Excelente link. Vai ter uma palestra do Ritter no qconsp.com sobre o assunto também.

    Sobre chamar o gc() diretamente, normalmente são situações para poder contornar bugs de API. O caso que você citou é um bug clássico que só foi fechado no Java 6, e atualmente não é mais necessário fazer isso:
    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6266377

  4. Lucas Souza 10/09/2010 at 01:52 #

    Oi Bruno, obrigado pelo comentário e pela dica do link. Quanto a chamar o GC explicitamente, é na linha que o Paulo disse mesmo, somente em casos que vc precisa driblar algum bug da API, caso contrário nao.

  5. Bruno Laturner 13/09/2010 at 14:38 #

    Agora que fui ver o link do Paulo, ainda tenho que fazer um teste para saber se foi realmente corrigido.

    De qualquer forma, ontem conversei com o Simon Ritter sobre o bug, passei um exemplo bem básico para ele:

    File file = (…)
    result = process(file);
    file.delete(); // false

    // really stupid, takes forever
    while (file.exits())
    file.delete();

    if (! file.delete()) {
    System.gc();
    file.delete(); // ok
    }

    Ele respondeu que esta última forma provavelmente é o melhor jeito de resolver o problema, pelo menos até o JDK7 sair, onde fizeram muitas melhoras na parte de IO.

  6. Paulo Silveira 13/09/2010 at 15:08 #

    Oi Bruno. Se o Simon disse, não tenho nem como começar a contestar :).
    Depois testa pra ver se funciona, eu achava que tinha sido corrigido. Alguém ja havia falado isso no GUJ também.

    A palestra dele foi muito boa mesmo. Não só entendi o G1, como ficou BEM mais claro o CMS.

  7. Bruno Laturner 13/09/2010 at 16:35 #

    Enquanto não sai a apresentação no QConSP do Simon no InfoQ, tem o Technical Session do JavaOne 2008 sobre o G1, que tem os mesmos slides usados por ele:

    http://developers.sun.com/learning/javaoneonline/j1sessn.jsp?sessn=TS-5419&yr=2008&track=javase

    No link multimedia tem o áudio da apresentação junto do slideshow.

  8. Paulo Silveira 15/09/2010 at 15:42 #

    Relembrando alguns pontos da palestra do Simon, tem duas opções que eu não conhecia e me pareceram MUITO interessantes:

    -XX:+UseBiasedLocking para que deixe o lock com uma thread caso ela peça demais pelo lock daquele monitor (e so vai pensar soltar quando perceber que alguem tambem precisa dele)

    -XX:+AggressiveOpts para ligar as opções que eles estão pensando em deixar como default em futuras versões. (antes eu achava que ela ligava opcoes ainda instaveis)

  9. Diego 20/09/2010 at 18:34 #

    Parabens, um otimo artigo. Abriu a mente… Precisa de algo assim para melhorar o meu projeto, agora tenho um norte a seguir. Continue assim.

  10. Yoshi 10/12/2010 at 10:32 #

    Oi pessoal, sei que os valores para esses parametros dependem de JVM e SO, mas existe alguma maneira de obter os valores que estão setados atualmente para uma JVM?

    Abraços

  11. Octacilio 31/07/2013 at 15:11 #

    Muito bom, vc está de parabens. Só que sou leigo e vc pode me dizer no caso do netbeans como configurar por exemplo o parametro do heap -Xms512. Estou tendo esse problema no usuario. No meu netbeans em propriedades tem opções da VM = -Djava.security.policy=applet.policy -Xms512m -Xmx1024 é assim que teria que colocar para executar fora do ambiente do netbenas. O projeto compilado levaria para outra maquina? Existe alguma maneira de saber quais parametros da jvm estão setados no usuario?
    Grato.

  12. Souza 07/10/2016 at 04:58 #

    Parabéns!
    Só queria saber como evitar que o Java fique entupindo a memória e não regride? Obrigado.

Deixe uma resposta