sábado, 23 de agosto de 2008

Tagged under:

Setembro - Mês do Java

Setembro é o Mês do Java no Brasil. De 10 a 30/09, SouJava, Sun e vários JUGs brasileiros realizam uma série de eventos em 14 cidades brasileiras, iniciando pelo JustJava e culminando no Sun Tech Days Brazil, ambos em São Paulo. Na Bahia, os grupos JavaBahia e SertãoJUG, com o apoio da InfoqJr e do CA BSI/PD, lideram a organização de mini-cursos e palestras internacionais e nacionais. Veja os detalhes do evento em Salvador.



Mini-cursos - Laboratórios da FIB

06/09/2008
- Desenvolvimento para dispositivos móveis (JME) - Diego Barreto (FLEM)
- Introdução a Desenvolvimento Web com JSF e JPA - Ramon Lopes (Embaixador Sun/UFBA)

13/09/2008
- Gerenciamento de Informações Distribuídas com JNDI - Daniel Costa (UEFS)
- Introdução a Aspectos - Fábio Costa (Avansys)

Palestras - 15/09 - Auditório da Fundação Luís Eduardo Magalhães - FLEM (CAB)

- Estendendo, expandindo e portando projetos da Comunidade ME - Roger Brinkley(Sun)*
- Java em dispositivos embarcados com o SunSPOT - Roger Brinkley (Sun)*
- Java, Software Livre e Governo - Elizier Santos (Serpro)
- Os programas educacionais da Sun Microsystems - Eduardo Lima (Sun)
- Uma Plataforma OpenSource SOA com JBoss e seus Beneficios - Edgar Silva (Red Hat)
  *Tradução simultânea

Clique aqui para inscrições e programação detalhada.

quinta-feira, 21 de agosto de 2008

Tagged under: , , ,

Computação Invisível com Sentilla

Para quem se interessa por Computação Móvel, Embarcada, Pervasiva, Ubíqua, ...., ou qualquer que seja o nome, esta é uma notícia interessante.

O Sentilla (um dos vencedores do Duke´s Award Choice esse ano) lançou o kit de desenvolvimento Perk (Sentilla's all-in-one pervasive computing kit), vendido por US$199.

Além disso, foi lançado um novo site para desenvolvedores chamado Sentilla Labs ), que provê um ambiente de colaboração para projetos em torno do Sentilla.

Alguns links úteis:
  • Um vídeo legal sobre o Sentilla:
  • Veja o que o JavaChampion brasileiro Bruno Ghisi fala em seu blog sobre o Perk em suas primeiras experiências de uso.
  • Sobre computação invisível, tivemos uma palestra muito boa de Alexandre Gomes no I Salvador Java Day.

sábado, 16 de agosto de 2008

Tagged under: , , ,

Artigo sobre BeansBinding na JavaMagazine

A última edição da revista JavaMagazine que chegou às bancas essa semana traz um artigo de dois integrantes do JavaBahia: Serge Rehem e Alexandre Lima. O artigo mostra o uso dos componentes da especificação BeansBinding para sincronizar propriedades de objetos de maneira mais fácil e produtiva, minimizando o típico uso de listeners. Mostra como usar as classes dessa especificação sem o suporte de uma IDE e, ao final, foi mostrado como usar o Netbeans para apoiar o uso dessas classes. Além de serem colaboradores do blog JavaBahia e terem escrito esse artigo para a revista, os dois prometem que essa união renderá ainda mais frutos para a comunidade Java na Bahia.

terça-feira, 12 de agosto de 2008

Tagged under: , ,

Resolvendo problemas de performance (parte 4 - final)

Finalizando essa série de posts (parte 1, parte 2 e parte 3), vamos conhecer a geração que até então tínhamos deixado de lado: a geração permanente (permanent generation).

Permanent Space

O Heap (espaços eden, survivor e tenured juntos ) é o local onde residem as instâncias das classes (objetos). Antes da JVM criar uma instância de uma classe no eden space, as informações sobre a classe em si são carregadas para a memória - um processo análogo ao dos meta-dados de um banco de dados. O espaço da memória que a JVM usa para armazenar esses bytecodes do arquivo .class é o espaço permanente (permanent space). A figura abaixo mostra a relação entre o espaço permanente e o heap.


O ideal é que o espaço permanente seja tão grande quanto a quantidade de classes que a nossa aplicação carrega; afinal, ler a classe da memória é obviamente mais rápido do que lê-las do disco. Os coletores serial e paralelo limpam essa área quando assim o desejam (baseado em estatísticas internas). Para garantir que as classes não sejam removidas do espaço permanente, existe o parâmetro -noclassgc. Esse parâmetro define que a JVM não fará coleta de lixo no espaço permanente e, consequentemente, não removerá os bytecodes das classes de lá.

Esse ajuste seria inteligente se não nos trouxesse uma preocupação: o que acontecerá quando o espaço permanente estiver cheio e precisar carregar uma nova classe? Pelo que já aprendemos, a JVM examinará o espaço permanente e verá que precisa de mais memória, realizando uma coleção maior. O GC limpará o heap mas não fará nada no espaço permanente - o que terá sido um esforço inútil. A JVM examinará novamente o espaço permanente, verá que está cheio e repetirá o processo novamente, e novamente, e novamente. Após um tempo (definido na JVM), uma exceção OutOfMemoryError será lançada e a aplicação encerrará. A figura abaixo ilustra esse processo quando esse parâmetro está ativado.


Esse problema acontece principalmente em aplicações web que carregam milhares de JSP's, e que são traduzidas em código Java, compiladas para bytecode e colocadas no espaço permanente antes de criar uma instância no heap.

A recomendação é dimensionar esse espaço de memória para que caibam todas as classes usadas pela sua aplicação e ter uma folga para classes que serão adicionadas no futuro. Os parâmetros para dimensionar o permanent space são -XX:PermSize e -XX:MaxPermSize, que respectivamente definem o tamanho inicial e o tamanho máximo do espaço. Exemplo:
C:/JDK_HOME/bin/java -XX:PermSize=64m -XX:MaxPermSize=256m -jar MyApp.jar

Coletor concorrente no espaço permanente

Quando ativamos o coletor concorrente (concurrent collector) para a nossa aplicação, temos que nos preocupar com uma característica intrínseca desse coletor: ele nunca limpa o espaço permanente. Contudo, você pode mudar esse comportamento com os parâmetros -XX:CMSClassUnloadingEnabled e -XX:CMSPermGenSweepingEnabled.

A coleta de lixo na área permanente aumentará o tempo de pausa e pode encobrir problemas em bibliotecas e API's usadas pela nossa aplicação. O ideal é que não ative esse parâmetros a não ser que tenha uma forte razão para isso. Limite-se a dimensionar o espaço permanente, aumentando o seu tamanho máximo, e monitore a aplicação para verificar o comportamento da memória.

É o fim!

Espero ter ajudado a conhecer o mundo do gerenciamento de memória do Java e, quem sabe, ajudar a resolver problemas de performance em sua aplicação.

Para saber mais sobre os assuntos que foram tratados aqui, sugiro que visitem os seguintes links:
Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning
Gerenciamento de memória em Java - Helder da Rocha - parte 1
Gerenciamento de memória em Java - Helder da Rocha - parte 2
Gerenciamento de memória em Java - Helder da Rocha - parte 3

Até a próxima!
Tagged under: , ,

Resolvendo problemas de performance (parte 3)

Após termos visto sobre o gerenciamento de memória e memory leaks, vamos agora ver como a escolha de um Garbage Collector pode melhorar o desempenho de nossa aplicação.

Considerações iniciais

Antes de continuarmos, vamos definir alguns termos que utilizaremos mais adiante:
  • Vazão (throughput) é a porcentagem de tempo total que não foi gasto na coleta de lixo, considerado sobre longos períodos de tempo; a vazão inclui o tempo gasto em alocação de objetos (ajuste de performance de alocação geralmente não é necessário);
  • Pausas são os momentos em que a aplicação aparenta não responder porque uma coleta de lixo está acontecendo;
  • Pegada (footprint) é o conjunto de trabalho de um processo, medido em páginas de memória e cache;
  • Prontidão (promptness) é o tempo entre a morte de um objeto (a coleta dele) e quando o pedaço de memória que ele ocupava torna-se disponível.
Os desenvolvedores têm diferentes requisitos de coleta de lixo. Por exemplo, vamos considerar que a métrica correta para um servidor web seja a vazão, desde que pausas durante a coleta de lixo são toleráveis ou simplesmente abafadas por latências da rede. Contudo, a existência de pausas mesmo curtas em um programa gráfico desktop pode afetar negativamente a interação do usuário. Em sistemas com memória física limitada, ou muitos processos, a pegada pode ajudar na escalabilidade; já a prontidão é importante para sistemas distribuídos, incluindo remote method invocation (RMI).

Em geral, o dimensionamento das gerações (visto no primeiro post) passa pela avaliação dessas considerações. Por exemplo, uma geração jovem (espaços Eden e Survivor) muito grande pode maximizar a vazão, mas custará pegada, prontidão e momentos de pausas. As pausas na geração jovem podem ser minimizadas usando um espaço menor ao custo de aumentar a vazão. Vale ressaltar que o tamanho de uma geração não afeta a freqüência de coleta de lixo e as pausas de outra geração.

Como é possível perceber, não há um modo correto de dimensionar as gerações. A melhor escolha é determinada pelo modo que a aplicação usa a memória e pelos requisitos de performance impostos pelo desenvolvedor.

Dimensionando os espaços de memória

Para dimensionar os espaços de memória, utiliza-se parâmetros que são passados para a Sun JVM no momento em que iniciamos nossa aplicação. Os mais utilizados são os parâmetros -Xms e -Xmx. Eles, respectivamente, definem a quantidade de memória inicial que será garantida para a JVM e a quantidade de memória máxima que poderá ser utilizada pela JVM. Você pode especificar os valores em kilobytes (k), megabytes (m) ou gigabytes (g). Exemplo:
C:\JDK_HOME\bin\java -Xms64m -Xmx1g -jar MyApp.jar

O seguinte diagrama ilustra a diferença entre espaço garantido (commited) e espaço virtual.


Na inicialização da JVM, uma parte da memória é reservada para o heap. O tamanho do espaço reservado é especificado pelo parâmetro -Xmx. Se o valor do parâmetro -Xms é menor do que o valor de -Xmx, nem todo o espaço reservado é imediatamente garantido pela JVM. O espaço que não foi garantido é chamado "virtual" no diagrama acima. Os espaços diferentes do heap (permanent generation, tenured generation e young generation) podem crescer até o limite do espaço virtual se for necessário.

O tamanho total da memória disponível é o mais importante fator a ser considerado na performance da coleta de lixo. O segundo fator mais importante é a proporção da memória total que estará dedicada à geração jovem (young). Quanto maior for a geração jovem, menos freqüentes serão as ocorrências de coleções menores. Mais ainda, uma geração jovem grande implica em uma geração estável (tenured) menor, o que irá aumentar as ocorrências de coleções maiores. A melhor escolha dependerá dos objetos alocados pela aplicação e da vazão, pausas, pegada e prontidão consideradas.

O parâmetro que controla o tamanho da geração jovem é o -XX:NewRatio. Por exemplo, se você definir -XX:NewRatio=3 para iniciar sua aplicação, significa que a proporção (ratio) entre a geração jovem (young) e a geração estável (tenured) is 1:3. Portanto, os tamanhos somados dos espaços eden e survivor serão um quarto do tamanho total do heap.

Resumindo, você pode usar os seguintes parâmetros para iniciar a sua aplicação (outros parâmetros existem e você pode consultá-los na documentação do Java):
  • Para definir a quantidade de memória inicial da aplicação: -Xms=64m
  • Para definir a quantidade de memória máxima a ser usada : -Xmx=1024m
  • Para definir a proporção entre a geração young e tenured : -XX:NewRatio=8

Coletores disponíveis na JVM

Mais que nunca, já está na hora de aprendermos a escolher um garbage collector apropriado que garantirá melhor performance à nossa aplicação. A Sun JVM, até a versão 6, possui três algoritmos diferentes de coleta de lixo:
  1. O coletor serial (serial collector) usa uma única thread para realizar o trabalho de coleta de lixo, e o faz com relativa eficiência. É indicado para máquinas mono-processadas pois não consegue utilizar as vantagens de máquinas multi-processadas, contudo pode ser usado nestas máquinas para aplicação que manipulem pequenos conjuntos de dados. Esse coletor é selecionado por padrão pela JVM quando detecta um hardware compatível ou através do parâmetro -XX:+UseSerialGC.
  2. O coletor paralelo (parallel collector) realiza coleções menores paralelamente para diminuir o overhead gerado pela coleta de lixo. É indicado para aplicações que manipulem conjuntos de dados maiores executando sobre máquinas multi-processadas. Este coletor foi introduzido no Java 5 e melhorado no Java 6 para permitir realizar coleções maiores em paralelo. Lembre-se disso quando for escolher esse coletor nessas versões da JVM. Esse coletor é selecionado por padrão pela JVM (5+) quando detecta um hardware compatível ou através do parâmetro -XX:+UseParallelGC.
  3. O coletor concorrente (concurrent collector) realiza toda a sua tarefa concorrentemente - enquanto a aplicação executa - para manter as pausas da coleta de lixo em patamares mínimos. É indicado para os mesmos casos do coletor paralelo e quando o tempo de resposta é mais importante do que a vazão, já que as técnicas de minimização de pausas podem reduzir um pouco a velocidade da aplicação. Esse coletor só é selecionado através do parâmetro -XX:+UseConcMarkSweepGC.
O coletor serial é o mais conhecido dos desenvolvedores Java. É o coletor padrão que acompanha o Java desde o início e tem sido constantemente melhorado a cada versão nova que é lançada. Se sua aplicação não estiver funcionando bem com esse coletor, mesmo após você ter feito o dimensionamento de memória, é recomendável que tente os outros coletores de acordo com as características de sua aplicação.

O coletor paralelo é similar ao coletor serial; a diferença é que ele usa múltiplas threads para aumentar a velocidade da coleta de lixo. Se sua JVM for da versão 5, somente coleções menores são realizadas em paralelo; se sua JVM for da versão 6, as coleções maiores também poderão serão realizadas em paralelo (mas isso não é padrão). Para forçar a coleção maior, você tem que adicionar o parâmetro -XX:+UseParallelOldGC.

Se muito tempo estiver sendo gasto na coleta de lixo pelo coletor paralelo, ele lançará uma exceção do tipo OutOfMemoryError (se 98% do tempo total for gasto em coleta de lixo e menos do que 2% da memória for recuperada). Se necessário, esse comportamente pode ser desabilitado pelo parâmetro -XX:-UseGCOverheadLimit.

O coletor concorrente é indicado para aplicações que preferem pausas muito curtas e podem compartilhar recursos do processador com o GC. Contudo, esse coletor diminui um pouco a velocidade da aplicação em máquinas com 1 processador (mesmo que dual core). Para minimizar esse impacto, é recomendado mudar o modo de trabalho desse coletor para incremental, usando o parâmetro -XX:+CMSIncrementalMode. Nesse modo incremental, o coletor concorrente realiza a sua tarefa em ciclos incrementais para diminuir o impacto na velocidade da aplicação. As estatísticas de desempenho mantidas pela JVM são utilizadas para prever os ciclos e podem ser ativadas nesse modo através do parâmetro -XX:+CMSIncrementalPacing.

Receita de bolo para a escolha do GC

1) Se a máquina que executa a aplicação tiver apenas 1 processador:

1.1) Se seu processador é single core:

1.1.1) Se sua aplicação for pequena (até 100 MB de dados manipulados na memória), use o coletor serial:
C:\JDK_HOME\bin\java -XX:+UseSerialGC -jar MyApp.jar
1.1.2) Se sua aplicação for maior, use o coletor paralelo:
C:\JDK_HOME\bin\java -XX:+UseParallelGC -XX:+UseParallelOldGC -jar MyApp.jar

1.2) Se seu processador é dual core:

1.2.1) Use o coletor paralelo:
C:\JDK_HOME\bin\java -XX:+UseParallelGC -XX:+UseParallelOldGC -jar MyApp.jar
1.2.2) Se a aplicação parecer congelar quando estiver executando (pausas), troque para o coletor concorrente:
C:\JDK_HOME\bin\java -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+CMSIncrementalPacing -jar MyApp.jar

2) Se a máquina tiver mais de 1 processador:

2.1) Use o coletor concorrente:
C:\JDK_HOME\bin\java -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+CMSIncrementalPacing -jar MyApp.jar

Escolher um GC adequado e dimensionar os espaços de memória não é suficiente se você "sujar" seu código injetando memory leaks. Antes de mexer com os parâmetros de inicialização da JVM, tente resolver os problemas de seu código observando se objetos não estão ficando presos na geração velha (old generation).

Até em breve

No próximo e último post, falarei do espaço de memória que até então não estávamos tratando: a geração permanente (permanent generation).

segunda-feira, 11 de agosto de 2008

Tagged under: , ,

Resolvendo problemas de performance (parte 2)

Continuando o nosso post sobre performance, vamos agora aprender a identificar memory leaks e como evitá-los.

Usando JConsole para monitorar a aplicação

A partir da versão 5 da plataforma Java, o JDK incluiu uma ferramenta para monitorar aplicações chamada JConsole. Essa ferramenta utiliza a tecnologia Java Management Extension (JMX) para coletar informações sobre performance e consumo de recursos do processo da JVM no sistema operacional. Essa ferramenta será substituída pela VisualVM a partir do JDK 6 update 7; na verdade, é o mesmo JConsole com uma interface gráfica melhorada baseada no Netbeans.

Para utilizar essa ferramenta, sua aplicação deve ser iniciada com o parâmetro que ativa o agente de monitoramento remoto (com.sun.management.jmxremote) . Exemplo:
C:/JDK_HOME/bin/java -Dcom.sun.management.jmxremote -jar MyApp.jar

Você também pode adicionar esse parâmetro para script que inicia o servidor Tomcat, JBoss ou qualquer outra aplicação Java que queira monitorar. A partir da versão 6 do JDK, esse parâmetro é opcional para utilizar o JConsole.

Para iniciar o JConsole, digite no console do sistema operacional:
C:/JDK_HOME/bin/jconsole

Se você estiver usando a JDK 6, verá a tela inicial como mostra a figura abaixo. Você pode escolher um processo Java local que esteja rodando na sua máquina ou um processo remoto (para conectar em processos remotos, consulte a documentação sobre JMX).


Após escolher o processo que você quer monitorar, aparecerá a tela abaixo com quatro gráficos. O primeiro gráfico, à esquerda e acima, mostra o consumo de memória. O segundo gráfico, à direita e acima, mostra a quantidade de threads rodando no seu processo. O terceiro gráfico, à esquerda e abaixo, mostra a quantidade de classes que foram carregadas para a memória (não confudir com instâncias delas que são os objetos). Finalmente, o quarto gráfico, à direita e abaixo, mostra o consumo da CPU pelo processo.


Observe que o gráfico de memória fica subindo e descendo a todo momento. Cada vez que a linha do gráfico cai significa que o Garbage Collector (GC) conseguiu remover objetos sem uso da memória. A figura abaixo aparecerá quando você clicar na aba Memory. Ela mostra o gráfico da memória, no caso em questão o consumo da área do heap, e permite a você ver cada espaço de memória individualmente, além de rodar o GC manualmente. É importante ressaltar que quando nós executamos o GC manualmente, uma coleção maior é que será executada e nunca uma coleção menor (discutimos esses assuntos no post anterior).


Agora que já temos a nossa ferramenta para enxergar o consumo de memória, aprenderemos como identificar se a queda de performance de nossa aplicação é na verdade um memory leak.

Identificando memory leaks

Memory leaks (gargalos de memória) são difíceis de enxergar sem uma ferramenta profiler adequada. Se a sua IDE já possui um profiler integrado, o melhor é utilizá-lo. Se não, a ferramenta JConsole poderá te ajudar nesse trabalho. Vamos considerar que você quer identificar memory leaks numa aplicação que você construiu usando um editor de texto simples (Notepad, Kwrite, etc).

Passos:
  1. Inicie a sua aplicação com o parâmetro que ativa o agente de monitoramento remoto;
  2. Inicie o JConsole e conecte-o ao processo da sua aplicação;
  3. Execute seu caso de uso (ou faça a requisição) para que a aplicação carregue todos os objetos necessários na memória;
  4. Observe o heap no gráfico do JConsole (ou capture a tela) e memorize a situação da memória; esse é o momento 1 - antes de executar seu caso de uso;
  5. Execute seu caso de uso novamente; as classes já deverão estar na memória mas os objetos serão criados novamente;
  6. Observe o heap no gráfico do JConsole (ou capture a tela) e memorize a situação da memória; esse é o momento 2 - depois de executar seu caso de uso;
  7. Compare os dois momentos e verifique se os objetos continuaram no heap após a execução do seu caso de uso;
  8. Force o GC e verifique novamente se a JVM conseguiu limpar os objetos sem uso.
Para melhorar a análise, realize os passos 5 e 6 várias vezes, comparando os momentos distintos (passo 7). Se o seu caso de uso termina e os objetos não estão sendo coletados após a realização dele, provavelmente você tem um memory leak em sua aplicação porque os objetos estão sendo retidos na memória por algum motivo.

O que pode causar um memory leak?

Entre as causas de um memory leak a mais comum de acontecer é a referência a um objeto que nunca é liberado, chamada também de object loitering. Para ilustrar esse efeito, observe o código a seguir que calcula o checksum de um arquivo.

 1: public class LeakyChecksum {
2: private byte[] byteArray;
3:
4: public synchronized int getFileChecksum(String fileName) {
5: int len = getFileSize(fileName);
6:
7: if (byteArray == null || byteArray.length < len)
8: byteArray = new byte[len];
9:
10: readFileContents(fileName, byteArray);
11: // calcula o checksum e retorna
12:
}
13: }

A decisão de transformar o buffer byteArray em variável de instância pode ter sido para melhorar o reuso do buffer e evitar criar esse objeto várias vezes na memória. O que aparentemente era a decisão correta se mostra errônea quando esse buffer nunca é liberado para a coleta de lixo porque ele sempre está alcancável pela aplicação (pelo menos até que a instância de LeakyChecksum seja coletada pelo GC). No pior caso, enquanto a instância é usada, o objeto LeakyChecksum permanentemente retêm um buffer tão grande quanto for o arquivo processado. Isso põe mais pressão no GC e requer coleções maiores frequentes. A solução para esse problema é deixar o buffer como sendo uma variável local ao escopo do método getFileChecksum(), assim o GC poderá completar o ciclo de vida desse objeto normalmente.

Outra forma de object loitering aparece quando arrays são usados para implementar estruturas de dados compostas. O código a seguir mostra uma implementação de uma pilha baseada em array.

 1: public class LeakyStack {
2: private Object[] elements = new Object[MAX_ELEMENTS];
3: private int size = 0;
4:
5: public void push(Object o) { elements[size++] = o; }
6:
7: public Object pop() {
8: if (size == 0)
9: throw new EmptyStackException();
10: else {
11: Object result = elements[--size];
12: // elements[size+1] = null;
13:
return result;
14: }
15: }
16: }

No método pop(), depois do ponteiro do topo ser decrementado, os elementos ainda mantêm a referência para o objeto que foi retirado da pilha. Isso significa que a referência para o objeto ainda está alcançável pela aplicação mesmo que o programa nunca mais a use novamente. Assim, o GC nunca coletará esse objeto até que a posição da pilha seja reusada em uma chamada futura ao método push(). A solução para esse memory leak é anular a referência do objeto que está sendo retirado da pilha, como mostra a linha comentada do código (linha 12).

Uma dica simples para evitar memory leaks é setar uma variável que não será mais utilizada no código para null. Assim, você está explicitamente dizendo ao GC que não quer mais o objeto e que ele pode ser coletado assim que o GC começar sua tarefa. Mas isso só não basta, temos que percorrer o nosso código observando o que pode atrapalhar o trabalho do GC. Lembre-se sempre: o GC só pode coletar objetos que não estão mais sendo usados, ou seja, referenciados. Se, de alguma forma, seu objeto ainda estiver sendo referenciado, ele é um candidato a permanecer no espaço Tenured por um longo tempo.

Outras dicas mais usuais para evitar problemas:
  • Quando usar coleções, depois que elas não forem mais úteis, chame o método clear(): isso fará com que os objetos sejam desreferenciados pela coleção;
  • Quando usar strings, depois que elas não forem mais úteis, referencie-as para null (por exemplo, str = null): isso fará com que seja explicitamente dito ao GC que você não as quer mais;
  • Utilize frameworks de cache de objetos como EhCache, principalmente se estiver usando o Hibernate;
  • Em sistemas web, sempre use pools de conexão: isso evita objetos de conexão com o banco de dados inúteis na memória.
  • Em sistemas web, evite o uso de escopo de sessão (session) e maximize o uso de escopo de requisição (request). Também é interessante baixar o tempo de expiração da sessão para evitar objetos presos no espaço Tenured.

Até a próxima

No próximo post, eu falarei dos tipos de Garbage Collector que existem na Sun JVM e quando usá-los para melhorar a performance de nossa aplicação, ou container. Até lá!

domingo, 10 de agosto de 2008

Tagged under: , ,

Resolvendo problemas de performance (parte 1)

É comum nos depararmos com aplicações construídas em Java que são lentas, pesadas e consumidoras vorazes de memória. Muitos, simplesmente, culpam a plataforma como a causadora de todos os problemas mas, na verdade, a culpa está sempre no desenvolvedor que não procura conhecer por que sua aplicação está sem performance.

Um dos problemas mais comuns de acontecer é o OutOfMemoryError, a única exceção em Java que não tem chance de ser tratada. Quando essa exceção é lançada, sua aplicação provavelmente sai do ar e não retorna.

Vamos conhecer mais sobre o mundo do Garbage Collector do Java e entender como podemos resolver problemas de performance em nossas aplicações.

Gerenciamento de memória da Sun JVM

A máquina virtual Java da Sun (Sun JVM) é geracional, ou seja, um objeto é criado num espaço inicial e muda para outros espaços, até que chegue num espaço onde possa morrer quando não for mais usado. A Sun JVM é dividida nos seguintes espaços: Young generation, Old generation, Permanent generation. Os dois primeiros espaços compõem o chamado Heap - onde os objetos são criados pelos desenvolvedores - e é onde nós vamos nos focar nesse momento.


O espaço Young generation - geração jovem - é subdividido em outros três espaços: o Eden space, o Survivor From space e o Survivor To space. Um objeto sempre é criado no Eden - literalmente no paraíso. Quando o Eden está cheio, o Garbage Collector (GC) percorre esse espaço e verifica se os objetos ainda estão sendo usados. Os que não estiverem mais sendo usados são removidos da memória e os que ainda estiverem em uso vão para o primeiro espaço de sobrevivência (Survivor From). Quando o Eden novamente fica cheio, o GC copia os objetos em uso para o segundo espaço de sobrevivência (Survivor To) e remove os objetos sem uso. Quando os espaços Survivor estão cheios, o GC copia os objetos que estiverem ainda em uso para o Tenured space - o espaço da estabilidade da geração velha (Old generation).



Esse processo de cópia que o GC realiza é conhecido como Copy Collection (coleta de cópia) ou Minor Collection (coleção menor). Quando o GC não pode mais realizar uma copy collection com os objetos (provavelmente os espaços já estão cheios), ele realizará uma Major Collection (coleção maior), também conhecida como Stop-The-World Collection (coleta pare-o-mundo). Durante essa coleta, o GC suspende todas as threads na Sun JVM e realiza uma busca na memória mais profunda para encontrar os objetos que realmente não estão mais sendo usados e, assim, liberar memória nos espaços.


Na implementação da Sun JVM, percebe-se que os objetos que estiverem no Tenured Space só podem ser removidos por uma coleta de lixo maior. Objetos que chegam até esse espaço de memória são objetos custosos e nós queremos evitar que isso aconteça para que a memória fique o mais limpa possível (diminuindo o trabalho do GC).

Memory leaks

Agora que já sabemos como funciona a memória, vamos entender o que é um memory leak (gargalo de memória). A memória está "engarguelada" quando um objeto mantêm uma referência indesejada para outro objeto, o que força o GC a não liberar a memória usada. Os objetos que não são desreferenciados farão seu caminho através dos espaços Eden e Survivor até chegarem à geração velha. Lá, eles ficarão e permanecerão porque o GC acha que os objetos ainda estão em uso. Para complicar, se isso acontecer num ambiente multi-usuário (como uma aplicação web), muitas requisições feitas usando um código que "enguargela" os objetos farão com que o tenured space cresça sem parar.

Para identificar um memory leak em nossa aplicação, temos que usar uma ferramenta Profiler. Algumas IDE's já possuem um profiler integrado mas, se não for o seu caso, a Sun JVM já traz um modo de você rastrear os seus objetos através do console do seu sistema operacional. Basta apenas iniciar a JVM com o seguinte argumento:
–verbose:gc

Quando sua aplicação estiver rodando, a JVM produzirá no console mensagens como essa:
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]


Aqui nós vemos duas coleções menores seguidas por uma maior. Os números antes e depois da seta (325407K->83000K na primeira linha, por exemplo) indicam o tamanho do objetos vivos antes e depois da coleta de lixo. O próximo número em parênteses ( (776768K) novamente na primeira linha) é a quantidade de memória realmente usada pelos objetos - não confudir com a memória que foi alocada no sistema operacional. Esse número não inclui a permanent generation (falaremos sobre ela em outra oportunidade). O último item da linha (0.2300771 secs) diz qundo tempo foi gasto pelo GC para fazer a sua tarefa.

Se você acrescentar os seguintes argumentos, você irá acrescentar mais detalhes às informações produzidas pelo GC, inclusive com referência temporal (timestamp):
–XX:+PrintGCDetails –XX:+PrintGCTimeStamps

A saída produzida no console parecerá com isso:
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]

Onde você vê a palavra DefNew significa que uma coleção menor foi realizada; onde se vê a palavra Tenured significa que uma coleção maior foi realizada. Após essas palavras, é mostrado o tamanho da memória antes e depois do GC naquele espaço e, no final da linha, sobre todo o heap. A informação no início da linha (111.042) é a referência temporal de quando aquela atividade aconteceu.

Até mais

Posteriormente, continuaremos esse assunto trazendo dicas de como evitar os memory leaks e como monitorar sua aplicação com a ferramenta JConsole que já vem na Sun JVM. Também aprenderemos mais sobre os três tipos de Garbage Collector existentes na Sun JVM e quando usá-los.