Começando com parâmetros e configurações da JVM
Postado em 09. set, 2010 por Lucas Souza em Java
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.
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.
ASSINE NOSSO RSS
Rodrigo Gomes
09. set, 2010
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
Bruno Laturner
09. set, 2010
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.
Paulo Silveira
09. set, 2010
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
Lucas Souza
10. set, 2010
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.
Bruno Laturner
13. set, 2010
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.
Paulo Silveira
13. set, 2010
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.
Bruno Laturner
13. set, 2010
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.
Paulo Silveira
15. set, 2010
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)
Diego
20. set, 2010
Parabens, um otimo artigo. Abriu a mente… Precisa de algo assim para melhorar o meu projeto, agora tenho um norte a seguir. Continue assim.
Yoshi
10. dez, 2010
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