Java.lang.OutOfMemoryError: Java heap space

17 respostas
J

Fala pessoal, então… estou rodando um codigo aqui que lê a resposta de um servidor HTTP e o método que eu vou colar mais abaixo é acessado cerca de 300 vezes atraves de um for.

Anteriormente eu estava guardando alguns codigos HTML dessas 300+ respostas em um objeto em memoria, e estava dando o erro mencionado no titulo dessa mensagem… Achei que era isso e comecei a gravar esses HTMLs em um arquivo, não instanciando nada novo durante a executação do metodo abaixo…

Agora eu estou sem saber oque fazer, pois já rodei aquele Profiler TPTP junto com o projeto pra ver se eu tinha algum objeto sendo instanciado muitas vezes, já debuguei o código, já setei tudo quanto eh variável que poderia estar dando problema pra =null, já forcei o garbage collector e mesmo assim esse erro acontece la pelo for 130-140…

o erro é sempre nessa linha aqui

achei que poderia ser algo a ver com uma resposta do servidor muito grande e tal, mas mesmo manipulando as chamadas do for pra ir direto ao for 130-140(que sempre gera a excessão) nada acontece… soh se eu comecar a rodar o for desde o 0 que a excessão eh gerada…

E cada vez que o programa passa por essa linha, a quantidade de memoria utilizada pelo javaw.exe aumenta muito mesmo… e não abaixa depois, mesmo se eu der um =null na variavel…

Alguem tem ideia do q pode ser isso? Sera que essas strings que to criando não estão sendo limpadas pelo Garbage collector mesmo depois de ninguem mais apontar pra elas? Sei la to perdido… passei o dia em cima disso e não sai do lugar :(( alguem me da uma luz por favor!!!

public String lerResposta(HttpMethod metodo)
{
       String responseBody = new String();	
		
       try
      {
          BufferedReader in = new BufferedReader(new InputStreamReader(metodo.getResponseBodyAsStream()));
          StringBuffer out = new StringBuffer();
          String linha;
		
          while ((linha = in.readLine()) != null)
          {
                out.append(linha).append("\n");
          }		
	
-----------> responseBody = new String(out.toString().getBytes(),"UTF-8");
      }
      
      catch (UnsupportedEncodingException e) {
       ...... }
      catch (IOException e) {
       ...... }
    
      finally
      {
           metodo.releaseConnection();
      }
       
       return responseBody;    	
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOfRange(Unknown Source)
	at java.lang.String.<init>(Unknown Source)
	at java.lang.StringBuffer.toString(Unknown Source)
	at Requisicao.lerResposta

17 Respostas

M
=null

Colocar isso numa variável não garante que ela será limpa naquele momento, aliás, só será limpa na segunda consequente vez que o GC executar.
O grande problema pode ser - e deve ser mesmo - a quantidade que você está permitindo para o uso do seu programa.
Acho que aqui poderás encontrar uma ajuda:
http://www.guj.com.br/posts/list/59976.java
Busca no fórum sobre java.lang.OutOfMemoryError

Até!

N

Assim fica bem melhor para a sua memória …

public String lerResposta(HttpMethod metodo)
 {
        String responseBody = new String();	
 		
        try
       {
           DataInputStream in = new DataInputStream(um inputstream);
           StringBuffer out = new StringBuffer();
 		
           while (in.avaliable &gt 0)
           {
                 out.append(lin.readLine()).append("\n");
           }		
 	
           responseBody = new String(out.toString().getBytes(),"UTF-8");
       }
       
       catch (UnsupportedEncodingException e) {
        ...... }
       catch (IOException e) {
        ...... }
     
       finally
       {
            metodo.releaseConnection();
       }
        
        return responseBody;    	
 }
M

Mais um para série dúvidas sobre o Garbage Collector:

A verdade sobre o GC

Até!

J

maquiavelbona:
=null
O grande problema pode ser - e deve ser mesmo - a quantidade que você está permitindo para o uso do seu programa.
Acho que aqui poderás encontrar uma ajuda:
http://www.guj.com.br/posts/list/59976.java
Busca no fórum sobre java.lang.OutOfMemoryError

Até!

Então maquiavelbona, se eu alocar mais memoria pra JVM o programa roda na boa sem gerar as excessões, só que nesse projeto eu chamo o metodo umas 300x, mas jah tenho outro projeto que vou ter que fazer algo parecido e acessar esse metodo umas 800+ vezes seguidas, e ai nao há memória que aguente… oque está me deixando maluco é oq está consumindo tanta memória… Debugando o projeto, eu notei que quando eu passo por essa linha aqui,

a memória gasta pelo java aumenta absurdamente e não diminui mais, só vai aumentando, aumentando, até gerar a excessão lá…

Então o GC não está “limpando” esse responseBody ? seria algo assim?

Estranho que eu chamo esse método de uma outra classe, e já verifiquei no TPTP que só tem uma instancia de “Requisicao” ( o nome da classe que abriga esse método ) em memória o tempo inteiro, quando eu chamasse o método novamente, essa variavel responseBody, por ser instanciada novamente, não teria que ir para o estado de Invisible/Unreachable ou whatever e ser eliminada da memória pelo GC? Não digo q ela teria que ser eliminada no exato momento que eu chamo o método de novo, mas até gerar a excessão de outofmemory, o GC não deveria já ter liberado a memória que essa variável estava usando?

Quando eu starto o programa, até ele chegar a invocar esse método a memória usada esta em 20mb, quando da o problema de OutOfMemory, o programa está usando 90 e poucos mega de memoria, e se eu aloco mais memória pra JVM, o meu laço for termina sem excessão lançada, mas tmbm com um uso de memoria de uns 150 mega ±… achei isso muita coisa pra um for que soh invoca esse metodo e vai salvando a resposta em um arquivo em disco…

sei lá… to ficando doido com isso já :frowning:

L

Olá

Seu programa tem coisas estranhas:

  1. A instrução
    
    String responseBody = new String();
    
    deveria ser:
    
    String responseBody;
    
  2. O método readLine() de DataInputStream foi deprecado

  3. Não sei exatamente a lógica do que quer fazer mas minha sugestão para você testar seria o seguinte:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

. . . . . . . . . . . . . . . . 

   public String lerResposta(HttpMethod metodo) {
	  
   String responseBody;	
	  		
      try {
         BufferedReader in = new BufferedReader(new InputStreamReader(um InputStream));
         StringBuilder out = new StringBuilder(512);
         String linha;
         while ((linha = in.readLine()) != null) {
            out.append(linha).append("\n");
         }		
	  	
	  responseBody = new String(out.toString().getBytes("UTF-8"));
      } catch (UnsupportedEncodingException e) {
	         ...... 
      } catch (IOException e) {
	         ...... 
      } finally {
          metodo.releaseConnection();
      }
	         
      return responseBody;    	
   }

Como se percebe, fiz algumas modificações rápidas sem pensar muito em otimização mas acho que mais eficiente do que seu código.

Veja as diferenças e verifique no JavaDoc as vantagens das classe que troquei.

[]s
Luca

J

Luca:
Olá

Seu programa tem coisas estranhas:

  1. A instrução
    String responseBody = new String();
    deveria ser:
    String responseBody;

Entao, se eu faco isso o programa não roda, pois diz que a variavel responseBody pode não ter sido inicializada

Eu não estava usando DatainputStream e sim BufferedReader, cujo o readLine não está deprecado, mas vou testar msmo assim

A lógica é bem simples, o metodo recebe um HttpMethod que eh a resposta de um requisicao GET a um servidor qualquer, dai eu leio essa resposta e jogo numa string pra retornar…

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

. . . . . . . . . . . . . . . . 

   public String lerResposta(HttpMethod metodo) {
	  
   String responseBody;	
	  		
      try {
         BufferedReader in = new BufferedReader(new InputStreamReader(um InputStream));
         StringBuilder out = new StringBuilder(512);
         String linha;
         while ((linha = in.readLine()) != null) {
            out.append(linha).append("\n");
         }		
	  	
	  responseBody = new String(out.toString().getBytes("UTF-8"));
      } catch (UnsupportedEncodingException e) {
	         ...... 
      } catch (IOException e) {
	         ...... 
      } finally {
          metodo.releaseConnection();
      }
	         
      return responseBody;    	
   }

Hoje vou tentar dividir as chamadas a esse método pra ver como vai ficar o consumo de memória, qualquer solução q eu conseguir eu posto aqui …

L

Olá

Faz String responseBody = null;

Agora é que reparei que não tinha respondido para você. Não entendi porque o Luis trocou sua instrução.

O que me deu dúvida foi a questão da necessidade de uso do charset UTF-8

Estamos torcendo e aguardando…

[]s
Luca

N

Totalmente desnecessário estar criando novos objetos String para logo após serem desalocados.

String linha;
while ((linha = in.readLine()) != null) {
      out.append(linha).append("\n");
}

Isso não é bom.

L

Olá

Por favor mostre alternativas sem usar algo deprecado por erro com a seguinte mensagem no Javadoc:

Javadoc DataInputStream.readLine():

Deprecated. This method does not properly convert bytes to characters. As of JDK 1.1, the preferred way to read lines of text is via the BufferedReader.readLine() method. Programs that use the DataInputStream class to read lines can be converted to use the BufferedReader class

[]s
Luca

J

entao… o Charset UTF-8 eu to usando pra ele decodar certo os ç, acentos e etc…

tentei usar o DataInputStream e continuou a mesma coisa em relação ao consumo de memória.

concorco com nbluis quanto a criar um string pra logo depois ser desalocado ( e depois dessa dor de cabeca que tive, tenho minhas duvidas se ela eh desalocada mesmo heheh ), mas como então eu poderia fazer isso sem usar String??

Agora a noticia boa é que resolvi o problema de memória… oq eu fiz foi mudar todo o processo do meu programa, esse metodo ai eh usado por um Crawler que to fazendo… eu acessava um endereco web, dae o programa me retornava todos os links encontrados nesse endereco, eu salvava o endereco desses links e partia pra outro site pra fazer a mesma coisa… e no final eu acessava link por link pra gravar em disco os HTMLs desses links que o Crawler capturou.

Oque eu fiz foi fazer o Crawler acessar um endereco, retornar os links da página acessada, e antes de eu partir pra outro site eu salvava os HTMLs desses links… o resultado ficou o mesmo, o codigo ficou o mesmo, soh tive que acrescentar mais um laco de repeticao na classe que chama esse metodo…

Mas oq eu nao entendi ainda eh pq isso resolveu o problema! hehehe

Se alguem souber pq, ficaria grato de saber tmbm até pra não repetir mais esse erro…

N

o DataInputStream tem um método readUTF() que te ajuda a fazer isso.

Mas se tu quer velocidade/performance utilize java.nio;

Até;

L

Olá

Desculpe-me mas acho que hoje devo estar meio lerdo.

O que há de errado construção abaixo: String linha; while ((linha = in.readLine()) != null) { out.append(linha).append("\n"); }

Alternativas?

Esta construçao é muitíssimo comum desde os tempos do C. Nunca vi nenhum problema.

Fora o Effective Java e o Refactoring, tenho mais 4 livros para ajudar na otimização de código:
Java Platform Performance
Java Performance Tuning
Java Pitfalls
Java 2 Performance and Idiom Guide

Em nenhum destes li que a alocação de uma String pudesse ser ruim em termos de desempenho.

Ainda acho esta solução muito melhor do que usar um método que a Sun diz que tem erros.

[]s
Luca (que talvez precise reaprender Java para não desperdiçar uma String…)

N

Luca:

Luca (que talvez precise reaprender Java para não desperdiçar uma String…)

Tu tá com algum problema?

N

Pena uma comunidade tão legal como o GUJ tenha alguém assim como moderador.

Como sempre se diz em casos destes.
Comentários desnecessários não são bem vindos.

Caso esteja com algum problema desligue o computador e vá tomar um café.

L

Olá

Eu só pedi uma alternativa e até agora ninguém me mostrou como fazer sem gastar uma String. Foi dito que algo não era bom sem mostrar porque. Fica parecendo comentário desnecessário.

Está claro agora onde eu quero chegar?

[]s
Luca

J

Calma pessoal… nao precisa se extressar por causa de um String tmbm…

Tipo, eu concordo com nbluis que eh desnecessário, mas enquanto eu não souber como fazer sem o String, o desnecessário torna-se necessário hehe
Assim como o Luca eu gostaria de saber como fazer essa leitura sem usar String…

Fui soh reparar agora tmbm que vc usou StringBuilder no lugar de StringBuffer, fui dar uma lida no javadoc e pelo que entendi a única diferença das duas classes eh que o StringBuilder nao garente sincronismo enquanto o StringBuffer sim, ou seja, o StringBuilder eh exatamente um StringBuffer só que otimizado para single thread, portanto no meu caso vale mais a pena usar StringBuilder já que por enquanto nao estou usando mais de um thread pra escrever no String, certo?

hehe to otimizando tudo que posso agora :smiley:

L

Olá

Exato. Repare também que inicializei o buffer com 512. Foi só para mostrar que há esta possibilidade. Para saber se faz alguma diferença era preciso medir os tempos. Acho só seria notada alguma mínima diferença se fosse para ler arquivos html com mais de 1000 linhas.

Você disse que é para um web crawler. É claro que você deve conhecer estes links. Vou incluir para que outros possam visitar:

Writing a Web Crawler in the Java Programming Language (é de 1998 e portanto muito antigo, vale só pelo pseudo código, segundo o forum da Sun o código Java não funciona mais)

Smart and Simple Web Crawler

Crawling the Web with Java Do livro The Art of Java, o código é bem feinho

Mais um achado pelo google que não tenho idéia se funciona

Lista do Carlos Perez

[]s
Luca

Criado 28 de maio de 2007
Ultima resposta 29 de mai. de 2007
Respostas 17
Participantes 4