[Dúvidas] OutOfMemoryError

15 respostas
eberson_oliveira

Olá,

Estou com uma dúvida sobre um determinado processo. Eu tenho uma rotina que onde eu preciso obter todo o conteúdo armazenado num resultset e passar para um objeto que vai fazer um determinado tratamento. Para evitar a carga do select toda de uma vez esse resultset foi sendo gerado através de um cursor de forma que passou a carregar todos os registros de 10 em 10; até ai tudo bem. Meu problema começou ao descobrir que esta rotina ainda seria capaz de lançar um OutOfMemoryError.
Fui dar uma olhada na rotina e notei que, aparentemente, o problema não deveria ocorrer (ao menos não pude encontrar nada que me sugerisse isso). Segue um algoritmo que demonstra o que a rotina realiza, basicamente, (não posso colocar o próprio código por causa de um contrato assinado com a empresa):

String[] colunas = colunas utilizadas no cursor
StringBuffer linha;
ResultSet rs;
BufferedWriter bf;

while ( ( rs = fetch 10 in cursor ).next() ){
	StringBuffer linha = new StrinBuffer();
	
	for( int i = 0; i < colunas.length; i++ ){
		linha.append( getConteudoProcessado( rs.getString( colunas[ i ] ) ) );
	}
	
	bf.write( linha.toString() );
	
	rs.close(); //coloquei isso pq achei que o ResultSet estava deixando recursos alocados
}

Esse StringBuffer recebe o append várias vezes ao invés de apenas 1... mas o seu conteúdo não chega a um valor significativo. O método getConteudoProcessado ilustra a situação onde o valor obtido do resultset é processado por vários objetos (atributos de instância) cuja finalidade é somente processar e retornar uma String.
Utilizei o seguinte comando: -XX:+HeapDumpOnOutOfMemoryError para tirar um "retrato" da memória no momento do OutOfMemory.
As minhas dúvidas são:

1 - O fato de sempre instanciar um StringBuffer novamente é melhor ou limpá-lo deveria ser a melhor estratégia? Pq?

2 - O BufferedWriter consegue descarregar o conteúdo em disco conforme seu buffer enche? Se sim, é necessário configurar um tamanho desse buffer ou o tamanho padrão deve servir?

3 - A chamda rs.close() é, realmente, necessária nesta situação?

4 - Se a VM sentir necessidade de alocar mais espaço, ela deve rodar o GC para tentar liberar algum espaço, correto? É possível que algum desses objetos não seja coletado?

5 - O Arquivo gerado pelo comando -XX:+HeapDumpOnOutOfMemoryError apresenta o estado da memória heap já com a execução do GC? Li em algum lugar que antes de lançar OutOfMemoryError a VM ainda executa o GC, mas o resultado do dump apresenta a memória após a sua execução ou pode ser que o GC ainda não tenha sido executado?

6 - Mesmo tendo apresentado esse trecho que representa o processo bem superficialmente, é possível dizer que um cursor cujo select tenha 4000 registros e 4 vezes mais rápido que um que possua 16000 registros?

Espero ter sido claro o suficiente,

Grato pela atenção,
Éberson

15 Respostas

P

Aumente a memória da vm: -Xms???m -Xmx???m
???=qtde de memória

eberson_oliveira

pozzo:
Aumente a memória da vm: -Xms???m -Xmx???m
???=qtde de memória

Mas isso “ocultaria”/adiaria o problema… estou tentando encontrar uma forma de resolvê-lo… mas está difícil até para identificá-lo

[]s

E

Use um profiler e rode o programa de modo que, antes de você chegar a essa condição (ponha um contador, ou alguma coisa que lhe permita pausar o programa antes de você ler a quantidade de registros que faça o programa tomar a exceção de falta de memória), veja o estado do profiler. O próprio JDK tem um “jvisualvm.exe” que pode ser usado como profiler. Isso lhe irá indicar que objetos ocupam mais espaço.

Acho que não é o StringBuffer que ocupe espaço (e não, não resolve você usar o método StringBuffer.reset e reaproveitar o StringBuffer. Simplesmente não ajuda muito). É alguma coisa na implementação do cursor usado; um cursor “forward-only” ocupa muito, muito menos espaço que um cursor que pode ser movido para frente e para trás, porque o “forward only” (que é o default, se não me engano) só precisa guardar o último registro lido.

J

O seu stringbuffer se tornou uma bomba relógio, ou seja, um memory leak.
Sobre o stringbuffer ser coletado pelo gc, a resposta é depende. Se a memória ainda está referenciada, o coletor não libera os recursos do seu stringbuffer.

Como já citado acima, use a jvisualvm, e crie um profiler, para ver qual varíavel cresce(gera o leak) no heap. O stringbuffer.append ae é um forte candidato ao leak.

eberson_oliveira

entanglement, dei uma olhada aqui… e apesar na utilizá-lo como “forward-only” o cursor utilizado permite navegar para frente e para trás. Vou fazer um teste e ver se o meu problema está no cursor.

juliocbq, quando eu dou um toString no StringBuffer e passando para o BufferedWriter continuo mantendo a referência a ele? Quando o conteúdo for escrito em disco o BufferedWriter não deveria liberar os objetos na memória?

Sobre utilizar um profile, estou tentando/apanhando da jvisualvm… confesso que não tinha criado um ponto onde pudesse interromper e verificar a memória… :oops: vou fazer isso…

E sobre o dump gerado no momento do outofmemoryerror? Eu tenho um e os objetos que consumiam mais memória eram objetos cujo escopo era de método. Estou verificando ainda se não os mantive vivo por algum motivo, mas me parece que o GC ainda não os havia coletado, por isso fiquei com dúvida. Vou listar os que mais ocorreram:

Class name Instances Size char[] 23% 27% java.lang.String 22% 14% org.postgresql.core.Field 18% 16% byte[] 17% 15%

Imaginei que esse char[] fosse gerado por causa do StringBuffer, no entanto, quando olhei as instâncias, notei que perteciam a objetos que foram utilizados em outro método. Caso não os tenha deixado vivo por engano, é possível que o pool de strings tenha crescido absurdamente?
E esse Field do postgres… o máximo que pude entender/verificar é que pertecem ao ResultSet, mas não entendi porque estão lá.

Vou precisar estudar mais sobre esses profiles…

valew,
Éberson

J

eberson_oliveira:
entanglement, dei uma olhada aqui… e apesar na utilizá-lo como “forward-only” o cursor utilizado permite navegar para frente e para trás. Vou fazer um teste e ver se o meu problema está no cursor.

juliocbq, quando eu dou um toString no StringBuffer e passando para o BufferedWriter continuo mantendo a referência a ele? Quando o conteúdo for escrito em disco o BufferedWriter não deveria liberar os objetos na memória?

Sobre utilizar um profile, estou tentando/apanhando da jvisualvm… confesso que não tinha criado um ponto onde pudesse interromper e verificar a memória… :oops: vou fazer isso…

E sobre o dump gerado no momento do outofmemoryerror? Eu tenho um e os objetos que consumiam mais memória eram objetos cujo escopo era de método. Estou verificando ainda se não os mantive vivo por algum motivo, mas me parece que o GC ainda não os havia coletado, por isso fiquei com dúvida. Vou listar os que mais ocorreram:

Class name Instances Size char[] 23% 27% java.lang.String 22% 14% org.postgresql.core.Field 18% 16% byte[] 17% 15%

Imaginei que esse char[] fosse gerado por causa do StringBuffer, no entanto, quando olhei as instâncias, notei que perteciam a objetos que foram utilizados em outro método. Caso não os tenha deixado vivo por engano, é possível que o pool de strings tenha crescido absurdamente?
E esse Field do postgres… o máximo que pude entender/verificar é que pertecem ao ResultSet, mas não entendi porque estão lá.

Vou precisar estudar mais sobre esses profiles…

valew,
Éberson

No final, tenta atribuir null ao StringBuffer, para que o ponteiro perca a referência.
Dá uma checada no vetor colunas…veja se ele é liberado também.

eberson_oliveira

Atribuí null ao meu StringBuffer… melhorou um pouco… antes dava o erro após carregar 10000 registros agora consegue carregar mais de 11000 registros… mas ainda fica dando OutOfMemoryError…

Eu comecei a coletar o estado da memória heap a cada 1000 registros carregados… mas os objetos que mais estão ocupando espaço são objetos que já deveriam ter sido coletados… ainda não entendi o que pode estar ocorrendo…

Notei que tem um trecho que onde é utilizado Reflection… um método é invocado via reflexão… esse recurso pode estar penalizando o meu sistema?
Vou dar uma pesquisada… sem alguém souber de algo que eu possa fazer pra resolver…
Estou ficando meio perdido… a rotina que gera o erro praticamente não apresenta os seus objetos no dump da heap… vou comecar a procurar pelo problema na rotina que faz uso de reflection…

[]s

eberson_oliveira

Alguém sabe dizer se tem como verificar quais objetos foram coletados pelo GC?

Estou pensando em tentar verificar o que ele coletou e o que não coletou… assim talvez fique mais fácil pra eu chegar a fonte do meu problema… pelo dump da memoria heap não estou conseguindo encontrar… o máximo que pude descobrir é que o problema deve estar no ResulSet, pois quase todas as referências tem Jdbc3ResultSet… mas não consigo descobrir onde está esse resultset…

grato pela ajuda,
Éberson

Fernando_Generoso_da

coloca aí sua classe de conexão…

eberson_oliveira

foi mal… mas eu não posso por o código real… normas da empresa =(…

Se ajudar… minha base de dados é Postgres 8.1… usamos Jdbc3 para a versão 7.4 (nao me critiquem, por favor… não fui eu que decidiu assim… estamos tentando mudar… mas tem um monte de coisa legada que ainda não podemos mexer)

o meu cursor consegue navegar para frente e para tras… esse código é utilizado em uma aplicação swing e em uma aplicação web baseada em servlets… ambas utilizam o mesmo componente… achei que o problema ocorria nos dois ambientes… mas notei que o problema não está ocorrendo na aplicação swing… .somente na web… será que pode ter a ver com o tomcat?

[]s

J

eberson_oliveira:
Alguém sabe dizer se tem como verificar quais objetos foram coletados pelo GC?

Estou pensando em tentar verificar o que ele coletou e o que não coletou… assim talvez fique mais fácil pra eu chegar a fonte do meu problema… pelo dump da memoria heap não estou conseguindo encontrar… o máximo que pude descobrir é que o problema deve estar no ResulSet, pois quase todas as referências tem Jdbc3ResultSet… mas não consigo descobrir onde está esse resultset…

grato pela ajuda,
Éberson

Coletados não dá pra saber, prq o ponteiro se perdeu. O que dá para saber são as gerações de objetos. 1,2,3 gerações

a geração 1 é coletada pelo gc se não houver referência, a partir do momento que o objeto sobe de geração, ele está não está sujeito a coleta(isso em %). Esse é o algoritmo.
Você pode ver quais objetos estão na geração 1, estes estão vivos e com boas chances de serem “abatidos”. Os da geração 3 são praticamente essenciais ao funcionamento do sistema.

vide visualgc - plugin da jvisualvm

eberson_oliveira

valew juliocbp,

vou dar uma pesquisa nesse visualgc e fazer mais testes… amanha posto o resultado que eu conseguir alcançar…

[]s

eberson_oliveira

bom…

eu peguei o visualgc… notei que quando eu rodo a aplicação no swing os objetos nem chegam a sair do eden… enquanto no tomcat o gc fica louco de tanto passar e não consegue coletar os objetos…
notei também que enquanto o gc passou várias vezes no swing… no tomcat passou poucas vezes, mas acredito que esse não seja o problema… já que fiquei tentando rodar o gc na mão e nada ocorreu…

estou tentando descobrir onde estão as referências que estão consumindo mais memória, mas ainda não entendi como indetificar pela visualvm

no dump da minha memória tem um trecho assim:

Total bytes: 58 119 702
Total classes: 4 744
Total instances: 1 658 699
Classloaders: 103
GC roots: 2775
Number of objects pending for finalization: 0

O que siginifica esses GC roots?

[]

J

eberson_oliveira:
bom…

eu peguei o visualgc… notei que quando eu rodo a aplicação no swing os objetos nem chegam a sair do eden… enquanto no tomcat o gc fica louco de tanto passar e não consegue coletar os objetos…
notei também que enquanto o gc passou várias vezes no swing… no tomcat passou poucas vezes, mas acredito que esse não seja o problema… já que fiquei tentando rodar o gc na mão e nada ocorreu…

estou tentando descobrir onde estão as referências que estão consumindo mais memória, mas ainda não entendi como indetificar pela visualvm

no dump da minha memória tem um trecho assim:

Total bytes: 58 119 702
Total classes: 4 744
Total instances: 1 658 699
Classloaders: 103
GC roots: 2775
Number of objects pending for finalization: 0

O que siginifica esses GC roots?

[]

por ae você não vai encontrar informações muito úteis. Crie um profile para ver que tipos de objetos estão ocupando mais memória. se der um duplo clique nos objetos, você pode navegar poe eles.

eberson_oliveira

Então… os 5 maiores objetos na memória no momento em que ocorreu o erro são os seguintes: (consegui isso pelo find… no painel ao lado de overview)

Class Name Retained Size

org.apache.commons.dbcp.PoolableConnection#1 4.884.597
java.util.ArrayList#2502 4.884.020
java.lang.Object[]#53001 4.884.000
org.apache.catalina.loader.WebappClassLoader#1 2.604.832
class java.lang.ref.Finalizer 392.360

Ao clicar para ver esse objeto ArrayList fui para guia de instância… ao verificar a referência vi que tem uma opção: “Show Nearest GC Root”… o que esse objeto significa? Como verifico onde ele se encontrava? Estou indo no caminho errado?

[]s

Criado 17 de março de 2010
Ultima resposta 18 de mar. de 2010
Respostas 15
Participantes 5