Java Heap Space no Hibernate [RESOLVIDO]

Pessoal,

tenho que fazer uma query que retorna em torno de 1 milhão de registros. Antes que alguém questione, sim ou precisar de todos os registros e sei que é uma carga muito grande, porém necessária. Já estou usando query nativa e tentei os seguintes cenário:

1 - query normal = (Java Heap Space)
2 - query com paginação, recuperando de N em N registros = (Java Heap Space)
3 - query com paginação, usando @ResultSetMapping = (Java Heap Space)
4 - query recuperando apenas as chaves primarias, e depois um loop para recuperar os dados restantes registro por registro = (Java Heap Space) (Esse cenário recuperou mais registros antes do estouro)

O Servidor tem 512MB de Heap, alguem tem alguma sugestão para esse caso?

1 - Aumente sua memória (não sei precisar o quanto seria necessário para esses milhões);

2 - Revise o passo 2 pra descobrir como que o resultado retornou. Se você paginou, mas trouxe tudo pra dentro da JVM de uma vez só, ou o primeiro resultado da paginação for muito alto, ainda assim pode vazar sua memória;

No mais, foi OutOfMemory ou PermGen Space?

Oi Adriano,

não posso aumentar a memoria, o servidor é do cliente e ele já negou o pedido.

o Problema é OutOfMemory.

No passo 2 o retorno foi um em arrays de arrays = registros x colunas. Faço varias queries em loop adicionando os resultados em um List.

A query paginada tem que funcionar.

Você liberava a memória quando trocava de pagina?

Como eu libero a memoria na paginação?
A paginação é feita em loop. Creio que a memoria só vai liberar quando sair do loop.

Existe uma maneira melhor de paginar? Que não seja em loop?

Faça a query com paginação, mas a cada consulta chame o Garbage Collection do java e de Commit (Hibernate commit) no banco para o banco liberar memoria isso sempre resolve. Ja tive esse problema.

Só um lembrete: não é recomendado chamar o GC via código (inclusive tem um parâmetro na JVM que desabilita essas chamadas).

Outro ponto: chamar o GC não é realmente chamar o GC. Isso é somente uma “dica” pra que ele, se possível, execute. Não há garantias de que ele vai executar e justamente por isso não é recomendado recair sobre o GC para resolver esse tipo de problema.

O melhor, nesses casos, é liberar as referências ao objeto utilizado. Se você, por acaso, estiver adicionando eles em uma coleção, isso não será possível. Não há problema algum você fazer o processamento no loop, desde que libere as referências aos objetos no final de cada iteração.

Agora, com 512MB de Heap, não dá pra fazer milagres pois você ainda tem que computar espaço para o stack das threads, o footprint do servidor de aplicações (se é que você está usando um), os outros objetos envolvidos na aplicação. No final você não terá os 512MB disponíveis para essa operação.

Se você adicionar um milhão de registros na lista, não adianta nada você fazer a paginação. A memória vai estourar do mesmo jeito. (Se foi bem isso que eu entendi com sua colocação.)

Você realmente precisa de todos os registros durante todo o processamento? Se sim, use um profiler para mensurar a quantidade de memória consumida neles para ter noção de quanto vai precisar porque não vai dar pra fazer milagres com 512MB de Heap. (Claro que depende do tamanho dos objetos que você aloca.)

Se você realmente precisar de todos esses objetos durante todo o processamento, considere distribuí-lo em outros processos (caso possível, já que você disse que não poderia aumentar a memória - lembrando que isso é um chute porque você não disse nada sobre o seu ambiente, por isso estou considerando a possibilidade de um cluster aí).

Só um lembrete: não é recomendado chamar o GC via código (inclusive tem um parâmetro na JVM que desabilita essas chamadas).

Outro ponto: chamar o GC não é realmente chamar o GC. Isso é somente uma “dica” pra que ele, se possível, execute. Não há garantias de que ele vai executar e justamente por isso não é recomendado recair sobre o GC para resolver esse tipo de problema.

O melhor, nesses casos, é liberar as referências ao objeto utilizado. Se você, por acaso, estiver adicionando eles em uma coleção, isso não será possível. Não há problema algum você fazer o processamento no loop, desde que libere as referências aos objetos no final de cada iteração.

Agora, com 512MB de Heap, não dá pra fazer milagres pois você ainda tem que computar espaço para o stack das threads, o footprint do servidor de aplicações (se é que você está usando um), os outros objetos envolvidos na aplicação. No final você não terá os 512MB disponíveis para essa operação.[/quote]

Concordo plenamente.

[quote=ninjasauro]Como eu libero a memoria na paginação?
A paginação é feita em loop. Creio que a memoria só vai liberar quando sair do loop.

Existe uma maneira melhor de paginar? Que não seja em loop?[/quote]

Faça algo como:

long totalRegistroNoBanco = // um countNaTablea long totalDePagainas = totalRegistroNoBanco / 50; // chutei 50 aqui for (0 ---> totalDePaginas){ List<Registro) registroList = consultaPaginada(dadosPaginacao); // seria tipo página 1, 50 registros // depois 2, 50 registros // trabalhe com a lista }

E aii Marcelo,

concordo contigo nas questão do GC e usar ele jamais passou pelo minha cabeça.

Mas enfim,

meu código está idêntico ao do Herbert enviado na ultima resposta e eu ainda seto nulo nos objetos no fim para liberar a memoria.

Porém agora eu usei uma query com ScrollableResults do hibernate e por incrível que pareça deu o OutOfMemory também. Não sei mais o que fazer:


                Session session = (Session) getEntityManager().getDelegate();
		org.hibernate.Query query  = session.createSQLQuery(sql.toString());

		configurarPaginacao....

		ScrollableResults result = query.scroll(ScrollMode.FORWARD_ONLY);
		
		List<Object > listaResultado = new ArrayList<Object >();
		
		while (result.next()) {
		    Object row = result.get();
		    Object[] vetorResultado = (Object[]) row;
			listaResultado.add(new Object(parametros de construtor...);
			

			session.evict(row);
			vetorResultado = null;
			row = null;
		}
		
		result.close();
		session.clear();

um Ponto importante que não sei se tem haver, é que o construtor do objeto do resultado tem mais de 30 parâmetros e fiz uns testes com apenas um parâmetro e funcionou. Essa quantidade de parâmetros no construtor não libera na memoria?

[quote=ninjasauro]E aii Marcelo,

concordo contigo nas questão do GC e usar ele jamais passou pelo minha cabeça.

Mas enfim,

meu código está idêntico ao do Herbert enviado na ultima resposta e eu ainda seto nulo nos objetos no fim para liberar a memoria.

Porém agora eu usei uma query com ScrollableResults do hibernate e por incrível que pareça deu o OutOfMemory também. Não sei mais o que fazer:


                Session session = (Session) getEntityManager().getDelegate();
		org.hibernate.Query query  = session.createSQLQuery(sql.toString());

		configurarPaginacao....

		ScrollableResults result = query.scroll(ScrollMode.FORWARD_ONLY);
		
		List<Object > listaResultado = new ArrayList<Object >();
		
		while (result.next()) {
		    Object row = result.get();
		    Object[] vetorResultado = (Object[]) row;
			listaResultado.add(new Object(parametros de construtor...);
			

			session.evict(row);
			vetorResultado = null;
			row = null;
		}
		
		result.close();
		session.clear();

um Ponto importante que não sei se tem haver, é que o construtor do objeto do resultado tem mais de 30 parâmetros e fiz uns testes com apenas um parâmetro e funcionou. Essa quantidade de parâmetros no construtor não libera na memoria?[/quote]

  1. Você viu oq o Ataxexe escreveu sobre GC?
  2. não, seu código não está igual ao meu. Em meu código a lista é liberada da memória, no seu você só adiciona. Se você ficar só adicionando, nunca vai funcionar com pouca memória. Você terá que aumentar a memória do servidor.
  3. Outra solução seria, com o tipo de paginação que eu passei você sintetizaria os dados em uma tabela temporária. Não tem como ajudar muito sem saber qual o requisito.

OBS.: Nenhum tipo de paginação do mundo vai te ajudar se você for pegar todos os resultados e jogar em memória. Dado em memória requer mais memória… sempre.

Oi Herbert,

realmente existe essa diferença mesmo. Eu libero todas as variáveis menos a lista com os dados pois eu preciso dela.
se efetivamente limpar ela ai funciona como você disse. A sua sugestão da tabela temporária seria gravar em arquivo no disco?

[quote=ninjasauro]Oi Herbert,

realmente existe essa diferença mesmo. Eu libero todas as variáveis menos a lista com os dados pois eu preciso dela.
se efetivamente limpar ela ai funciona como você disse. A sua sugestão da tabela temporária seria gravar em arquivo no disco?[/quote]
Eu salvaria em uma tabela no banco que poderia ter os dados apagados após o processamento.

Seria algo como:

// busca os dados paginando de 50 em 50 (por exemplo)
// faz um sumário desses dados e persiste na tabela temporária
// ao terminar o loop em todos os registros
// trabalhar em cima dos dados dessa tabela temporária
// drop na tabela temporária

Por que você precisa ter esse 1 milhão de objetos na memória ao mesmo tempo?

Que tipo de processamento fará com essa lista?

Chutando duas situações:

  • Está agregando esses valores de alguma forma: Não dá pra agregar direto no banco?

  • Está processando item por item da lista sem um precisar de outro: Experimente retornar um iterator ao invés de uma lista fazendo lazy loading dos dados do banco (paginando resultados).

Pessoal,

consegui resolver o problema da seguinte maneira.
Fiz a correção que o Hebert citou para limpar da memoria da lista completo de registros.

Agora eu não guardo mais os registros em uma lista.
Agora eu faço a query paginada e serializo cada registro em um arquivo. Cada pagina da paginação ficou referente ai um arquivo serializado. Assim posso liberar a memoria da lista que estava estourando a memoria.

Fiz a serialização dos objetos com o java.io.ObjectOutputStream e está funcionando sem problemas.

Preciso desses registros para geração de um relatório. O único trabalho depois é desserializar os arquivos mas o ObjectOutputStream trabalha muito bem com isso.

No mais obrigado a todos que tentaram ajudar.