Problema de Memória no Windows XP

15 respostas
Augusto_

Caros,

Escrevi uma aplicação em J2SE que faz o seguinte:

[list]

Desenha uma imagem em um painel usando o método drawImage();

Tem uma classe controladora que muda a foto que este painel desenha quando uma determinada ação é executada;

[/list]

O problema é o seguinte:

No windows 7, o programa funciona normalmente com muitas interações, ja cheguei a trocar as fotos mais de 50 vezes e não deu problema nenhum.
No windows XP, já na segunda vez que eu mando trocar a foto, ele da esse erro de memória:

OutOfMemoryError

O windows 7 tem 2 Gigas de memória, e o windows XP tem apenas 512 Megas, mas ambas as máquinas virtuais são inicializadas com o padrão, ou seja, 128 Megas de memória.

Alguém pode me dar uma luz sobre por que estou tendo esse problema? Ambos os programas são iguais e estão usando a mesma versão da JVM.

15 Respostas

E

Mesmo você passando os parâmetros -Xms e -Xmx iguaizinhos, alguns outros parâmetros são acertados de acordo com o tipo da CPU e do sistema operacional automaticamente pela JVM.
Pelo que imagino, os parâmetros que foram acertados automaticamente não deram muito “certo” no Windows XP.
No Windows XP, você pode tentar o parãmetro -Xincgc, mas pode ser é que seu programa fique muito mais lento.
Provavelmente as duas máquinas têm drivers de vídeo diferentes.
Pode ser, por exemplo, que você esteja com um vazamento de recursos que foi percebido mais rapidamente no Windows XP.

Carlos_ds_jar

Augusto_:
Caros,

Escrevi uma aplicação em J2SE que faz o seguinte:

[list]

Desenha uma imagem em um painel usando o método drawImage();

Tem uma classe controladora que muda a foto que este painel desenha quando uma determinada ação é executada;

[/list]

O problema é o seguinte:

No windows 7, o programa funciona normalmente com muitas interações, ja cheguei a trocar as fotos mais de 50 vezes e não deu problema nenhum.
No windows XP, já na segunda vez que eu mando trocar a foto, ele da esse erro de memória:

OutOfMemoryError

O windows 7 tem 2 Gigas de memória, e o windows XP tem apenas 512 Megas, mas ambas as máquinas virtuais são inicializadas com o padrão, ou seja, 128 Megas de memória.

Alguém pode me dar uma luz sobre por que estou tendo esse problema? Ambos os programas são iguais e estão usando a mesma versão da JVM.

Tente usar o comando

System.gc(); //Chama o garbage colletor do Java

Toda vez que trocar de imagem, depois posta aqui e diz se deu certo… 8)

Augusto_

Olá,

fiz aqui um teste no windows xp e fiquei monitorando o uso de memória.

O windows XP puro fica gastando 100 megas, e quando eu inicio o java e coloco a primeira foto, ele chega a quase 228 megas (ou seja, o java ja chega aos 128 megas de uso de memoria)

Algumas coisas relevantes, antes de falar da conclusão:

[list]A aplicação inicial mostra duas fotos de cada vez, e não uma;[/list]
[list]Fiz um teste colocando pra mostrar uma foto só, ao invés de duas;[/list]

O que acontece nos testes:

[list]O Programa inicia ocupando algo em torno de 70 Megas de memória;[/list]
[list]Depois da primeira interação, ele comeca a ocupar algo em torno de 100 Megas;[/list]
[list]Depois da segunda interação, ele ocupa muito perto dos 128 Megas;[/list]
[list]Da terceira em diante, o uso de memória não muda.(Garbage collector funcionando?).[/list]

O interessante é que o programa com uma foto só de cada vez não deu erro algum. Pra mim isso indica que de alguma forma o programa precisa de mais memória pra funcionar no XP do que no Windows 7.

O que eu acho que acontece quando eu coloco duas fotos:

[list]O programa entra no sistema ocupando os 70 megas;[/list]
[list]Na primeira interação, ele já chega nos quase 128 megas;[/list]
[list]Na segunda interação, ele tenta arrumar espaço pra colocar as novas fotos, mas não consegue, por que não tem lixo para o Garbage Collector tirar;[/list]
[list]Da exception e o programa trava.[/list]

Bem, se isso estiver certo, só o fato de forçar a JVM a usar 256 Megas de memória, ao invés de 128, já deve resolver o problema.

Será que o Windows XP realmente precisa de mais memória do que o windows 7 pra rodar esse programa?

adrianoneres

Tente usar o comando
view plaincopy to clipboardprint?
System.gc(); //Chama o garbage colletor do Java

Toda vez que trocar de imagem, depois posta aqui e diz se deu certo…

Posso estar falando besteira, mas fazer isso não garante que o Garbage Collector seja executado, isso apenas indica seu uso, mas não garante realmente que ele seja usado. Até onde sei, erros de Memória assim, como o OutOfMemory não são classificados como Exceptions, por isso, não é provável que você consiga tratá-los como se fossem. OutOfMemory, assim como Exceptions, herdam de Throwable, mas acredito que isso seja classificado como Error e não como Exception. Esse tipo de Erro é incluido nessa categoria por ser uma coisa aonde não se tem muito o que fazer, e falta de memória é uma delas, pois é considerado pelo Java um problema que não pode ser resolvido através de sua programação (Pouca memória é uma coisa que deve ser resolvida fora do sistema, adicionando-se mais memória à própria máquina aonde o sistema é executado).

Não sei se estou certo, mas espero ter dado uma luz. Procure uma forma de tratar isso sem enxergá-lo como uma Exception.

Abraço, até mais, e sempre posta ai os resultados que vc conseguir pra gente discutir e saber resolver se isso cair nas nossas mãos tbm.

Augusto_

adrianoneres:
Tente usar o comando
view plaincopy to clipboardprint?
System.gc(); //Chama o garbage colletor do Java

Toda vez que trocar de imagem, depois posta aqui e diz se deu certo…

Posso estar falando besteira, mas fazer isso não garante que o Garbage Collector seja executado, isso apenas indica seu uso, mas não garante realmente que ele seja usado. Até onde sei, erros de Memória assim, como o OutOfMemory não são classificados como Exceptions, por isso, não é provável que você consiga tratá-los como se fossem. OutOfMemory, assim como Exceptions, herdam de Throwable, mas acredito que isso seja classificado como Error e não como Exception. Esse tipo de Erro é incluido nessa categoria por ser uma coisa aonde não se tem muito o que fazer, e falta de memória é uma delas, pois é considerado pelo Java um problema que não pode ser resolvido através de sua programação (Pouca memória é uma coisa que deve ser resolvida fora do sistema, adicionando-se mais memória à própria máquina aonde o sistema é executado).

Não sei se estou certo, mas espero ter dado uma luz. Procure uma forma de tratar isso sem enxergá-lo como uma Exception.

Abraço, até mais, e sempre posta ai os resultados que vc conseguir pra gente discutir e saber resolver se isso cair nas nossas mãos tbm.

Você está certo. Primeiro que o System.gc() não garante que o sistema vai chamar o garbage collector na hora. Segundo que se, por algum motivo, o recurso ainda estiver sendo referenciado na memória, ele vai ser ignorado e continuará ocupando o seu espaço.

Infelizmente eu não conheço nenhuma maneira de garantir que um recurso não esteja sendo apontado por ninguém dentro do Java. E também não conheço nenhuma maneira manual de liberar uma determinada região de memória que o programador tenha certeza que não vai usar. Em C/C++ o free() e o delete() fariam isso.

Eu acho que a possibilidade do programador fazer isso na mão deveria ser item obrigatório em Java, por que nós acabamos tendo muitos problemas de desempenho por causa disso. Infelizmente.

Você também falou sobre ser mais memória. O problema é que, se o programa alocar memória recursivamente, sem o poder de liberar essa memória na hora que o programador quiser, esta encheria denovo com muita facilidade.

Obrigado pela ajuda de todos.

ViniGodoy

Configure a VM para gerar um heap dump quando der OutOfMemoryError. Basta usar o -XX:+HeapDumpOnOutOfMemory

Depois, analise o dump no Netbeans ou no Visual VM. Lá você consegue achar exatamente quem está referenciando suas variáveis. Com certeza, se a memória não está sendo liberada, é porque existe uma referência.

Eu trabalhei com Java e sistemas de tempo real por seis anos. O garbage collector é extremamente confiável e sempre roda e libera memória antes de fazer um OutOfMemoryError. Se algo puder ser liberado, vai ser liberado.

O funcionamento dele também é extremamente eficiente. É muito difícil você conseguir um desempenho melhor do que ele com o new e com o delete do C++. Dizer que o garbage collector gera “muitos problemas” de desempenho é um tremendo exagero.

Evidentemente, o C++ é mais otimizável que o Java. Mas fazer um programa de melhor performance exige extremo esforço, e não é para qualquer um.

É graças a impossibilidade de não dar delete no Java que também não temos problemas como dangling pointers no Java. E acho que essa tranquilidade mais do que compensa os eventuais problemas de performance.

Augusto_

ViniGodoy:
Configure a VM para gerar um heap dump quando der OutOfMemoryError. Basta usar o -XX:+HeapDumpOnOutOfMemory

Depois, analise o dump no Netbeans ou no Visual VM. Lá você consegue achar exatamente quem está referenciando suas variáveis. Com certeza, se a memória não está sendo liberada, é porque existe uma referência.

Eu trabalhei com Java e sistemas de tempo real por seis anos. O garbage collector é extremamente confiável e sempre roda e libera memória antes de fazer um OutOfMemoryError. Se algo puder ser liberado, vai ser liberado.

O funcionamento dele também é extremamente eficiente. É muito difícil você conseguir um desempenho melhor do que ele com o new e com o delete do C++. Dizer que o garbage collector gera “muitos problemas” de desempenho é um tremendo exagero.

Evidentemente, o C++ é mais otimizável que o Java. Mas fazer um programa de melhor performance exige extremo esforço, e não é para qualquer um.

É graças a impossibilidade de não dar delete no Java que também não temos problemas como dangling pointers no Java. E acho que essa tranquilidade mais do que compensa os eventuais problemas de performance.

Não vamos começar uma discussão aqui =) mas eu concordo que em C/C++ é necessário muito cuidado pra dar free e delete, cuidados que podem dar problema, mas eu sou sempre a favor do cara ter a opção. Poderia não ser necessário usar delete, mas ter a possibilidade de usar, desde que se entendam os riscos… Além disso, o garbage colector verifica se algo pode ser desalocado antes de dar erro por falta de memória, mas esse processo demora muito. Aqui estava demorando ateh 7 segundos pra me dar alguma resposta…

Gostei da dica do heap dump. Ainda vou precisar usar isso um dia =) mas acredito que o meu problema agora não seja esse. Aumentei um pouco a memória da JVM e o problema foi totalmente resolvido. Já fiz umas 30 interações e tudo funciona perfeitamente. Já fiz varios testes.

Ainda gostaria de confirmar se existem otimizações de memória no Windows 7 que façam ele precisar de memos memória do que o Windows XP pra rodar os programas.
Se alguém souber de alguma coisa, me avise =)

Abraços,
Augusto Escobar

ViniGodoy

Na verdade, “muito cuidado” é generosidade da sua parte. Tem que ter quase paranóia absoluta. E ainda vai sobrar um leak ou outro… hehehehehehe
Não é à toa que a comunidade C++ tem recomendado o uso de smart pointers, e outros mecanismos que implementam uma espécie de garbage collection no Java. Usamos conceitos como RAII para evitar ao máximo termos que controlar nós mesmos a gerência de recursos.

O garbage collector fica lento quando a memória está cheia, não por ele ser lento para encontrar a área de memória, mas pq ele troca da coleta rápida para a coleta completa. Ele faz isso numa tentativa desesperada de liberar memória. O algoritmo do garbage collector é mais guloso, mas muitíssimo mais rápido que o new e delete do C++. Ele divide a memória em duas zonas, uma de objetos de vida longa e outra de vida curta. Os de vida curta, sofrem reciclagem com relativa frequência, e são velozmente indexados. O custo de deleção deles é nulo, e o de realocação praticamente também.

Como o GC controla áreas largas de memória pré-alocada, ele incorre em bem menos custos de memória no SO, e ainda reduz consideravelmente perdas de cache. Não é à toa que a performance de uma linked list no Java tem um desempenho muito melhor que a mesma estrutura de dados no C++. Claro que isso tem um custo, o garbage collector ocasionalmente congela o sistema. Se você tiver a quantidade de memória extra para ele trabalhar (o que você provavelmente não tinha), essa demora é despresível. É possível ligar o profiling e ver as execuções do gc, e são impressionantemente rápidas. O G1, que vem no Java 7, ainda promete garantias de execução melhores.

Um benchmark bobo, mas que demonstra esse conceito, que gosto de sugerir é a criação/exclusão de milhares de objetos no heap. No java essa tarefa é virtualmente instantânea. No C++… bem, leva alguns minutos, pois ele cegamente recorre ao SO. Claro, seria possível sobrecarregar o new e delete e implementar algo mais inteligente, ou recorrer ao stack - mas isso só se você for o dono do código.

Não sei se você já leu os artigos do goetz sobre o funcionamento do gc, ou mesmo o paper sobre o g1. São muito interessantes. Eu geralmente recomendo à todos que gostam de ir um pouco mais fundo no assunto performance:



http://research.sun.com/jtech/pubs/04-g1-paper-ismm.pdf

Enfim, o problema é que quando a memória reservada à VM está cheia, o gc começa a compactar o heap, para tentar liberar até o último fio de cabelo disponível. Ele também faz mais coletas na zona de objetos de vida longa. E isso deve ter ocasionado os tais 7 segundos que você observou. E que, vale observar, são gastos arbitrariamente, sem qualquer controle do programador (o que é o pânico para quem trabalha com sistemas de tempo real, como nós dois).

Outro ponto muito positivo do Java em relação ao C e ao C++ no quesito performance é a quantidade de bons profilers não intrusivos de boa qualidade. Como por exemplo, o Visual VM e o profiler do Netbeans, ou o projeto TPTP do Eclipse. Eles dão relatórios precisos de onde o problema se encontra, sem tantas distorções como faz o Valgrind. Aliás, recomendo fortemente que você observe o gc num desses profilers, é muito interessante. Dá para passar alguns dias brincando e fazendo testes.

Só um detalhe. Não estou defendendo o Java, até porque o C++ é uma das minhas linguagens favoritas. Os dois tem ergonomia muito diferente, é idiotice mesmo iniciar uma “guerrinha”. É muito difícil fazer uma aplicação no Java que seja econômica em recursos, por exemplo. Ele come memória para valer. Ou, por exemplo, fazer uma aplicação java para controle real time na casa dos milisegundos. Só acho que no tema “velocidade de execução” o java ainda sofre muito pré-conceito, a maioria dos quais baseados em meias verdades, ou em versões muito antigas da tecnologia que já não são mais assim.

Augusto_

Depois vou ver com mais calma o material que você me passou. Eu também gosto muito de diferentes linguagens. Acho que cada uma tem suas particularidades. O desempenho do Java não é um preconceito, é uma dura realidade, infelizmente. O que acho é que 90% dos programas que o pessoal faz não precisam realmente do desempenho que as pessoas acham que precisam, por isso o java é fantastico. Facilita muito.

Problemas de alocação e desacolcação de memória aparecem muito quando lidamos com processamentos gráficos ou com programação dinâmica. A diferença de desempenho, nesses casos, é uma monstruosidade :). Não é a toa que 95% dos jogos de classe AAA (como crysis, e outros que vocês devem conhecer) são feitos em C/C++.

Enfim, se eu não adorasse Java, eu não estaria aqui :P. Valeu pelo material. Quanto mais conhecimento melhor.

ViniGodoy

Acho que a razão não é só essa. Aliás, essa é uma das menores delas, pois muitos jogos hoje usam linguagens de script, muito mais lentas que o Java. A maior parte dos gargalos está no hardware de vídeo a GPU, não na CPU. E isso deixa o processador da máquina livre para processar o java, o gc, e muitas outras coisas mais.

Claro que as APIs mais críticas seriam implementadas em C ou C++. Acho que delas realmente não tem como fugir, mas um binding leve sobre elas resolveria o problema, como já ocorre com muitos jogos hoje. Um pouco de hibridismo sempre vai ocorrer, principalmente em áreas como a física e os gráficos.

Mas principais razões, mesmo, na minha opinião, são que:

a) A indústria de consoles é totalmente avessa ao Java. Como a vantagem dos consoles é diferenciação de hardware, não faz sentido em falar de VM por lá. O conceito de uma VM é totalmente antagônico ao negócio deles. O C e o C++ são as únicas linguagens que tem compiladores para todos os consoles;

b) Legado. A indústria de games tem muita coisa feita em C++. Seria estupidez jogar tudo fora e sair portando para java. Já existem bibliotecas excelentes, como a SpeedTree e o Havoc. Agora, dê uma olhada no que bibliotecas como o JMonkeyEngine são capazes de fazer no Java, ou a Unity 3D faz no C#. É de tirar o chapéu. Mas como quase sempre, só atendem a PC e a um ou outro console.

Mas eu não confiaria o controle de uma caldeira fervendo ao Java. Ou não retiraria o disclaimer do próprio contrato de licença do Java de que ele não deve ser usado em usinas atômicas. :slight_smile:
Não tanto pelo desempenho geral, mas pela imprevisibilidade que o gc adiciona à aplicação.

Como eu falei, trabalhei com sistemas de tempo real com ele por muitos anos. Em muitos casos, o desempenho dele superava o do C++, por isso ainda afirmo que seja pré-conceito. Em outros casos o java perde, e feio. De novo, não dá para ser enfático e dizer simplesmente que ele é mais lento ou mais rápido e ponto, a ergonomia das duas linguagens é muito diferente.

Claro que sempre é possível fazer software mais rápido no C++, mas o custo que isso exige pode ser astronômico. Sem falar que o resultado depois pode ser muitíssimo caro e difícil de manter.

Augusto_

se quiser continuar a discussão… (tinha um email aqui… editado :P)

ViniGodoy

Anotado. Agora tira o seu e-mail dali antes que os webcrawlers achem. xD

Augusto_

prontinho

J

Isso provavelmente é um leak. Causado por algum componente se não me engano, pode até ser um jlabel, se você estiver pintando a imagem nele.
Use o java2d para pintar a imagem e dê um dispose nele. O jlable acumula muita memoria nesse tipo de trabalho.

diego_qmota

Só um adendo pessoal:

Você consegue capturar um OutOfMemoryError de forma similar ao que faz com o Exception:

try {

   //código que sob certas condições pode disparar um erro de memória

 } catch (OutOfMemoryError error) {

    //Tratamento do erro aqui

 } catch (Exception e) {

    //Tratamento de exceções e classes derivadas da classe Exception
 }

O que você não pode fazer é capturar o erro dentro do catch que captura Exceptions…

Criado 26 de julho de 2010
Ultima resposta 29 de jul. de 2010
Respostas 15
Participantes 7