A documentação para java.lang.Error
diz:
Um erro é uma subclasse de Throwable que indica problemas sérios que um aplicativo razoável não deve tentar detectar
Mas como java.lang.Error
é uma subclasse de java.lang.Throwable
, posso pegar esse tipo de Throwable.
Eu entendo por que não é uma boa ideia capturar esse tipo de exceção. Pelo que entendi, se decidirmos capturá-lo, o manipulador de captura não deve alocar nenhuma memória por si mesmo. Caso contrário OutOfMemoryError
, será lançado novamente.
Então, minha pergunta é:
- Existem cenários do mundo real em que pegar
java.lang.OutOfMemoryError
pode ser uma boa ideia? - Se decidirmos capturar
java.lang.OutOfMemoryError
, como podemos ter certeza de que o manipulador de captura não aloca nenhuma memória por si mesmo (nenhuma ferramenta ou prática recomendada)?
java
try-catch
out-of-memory
Denis Bazhenov
fonte
fonte
Respostas:
Eu concordo e discordo da maioria das respostas aqui.
Há uma série de cenários em que você pode querer pegar um
OutOfMemoryError
e, na minha experiência (em JVMs do Windows e Solaris), raramente éOutOfMemoryError
a sentença de morte para um JVM.Há apenas um bom motivo para detectar um:
OutOfMemoryError
fechar normalmente, liberando recursos de maneira limpa e registrando o motivo da falha da melhor maneira possível (se ainda for possível).Em geral, isso
OutOfMemoryError
ocorre devido a uma alocação de memória em bloco que não pode ser satisfeita com os recursos restantes do heap.Quando o
Error
é lançado, o heap contém a mesma quantidade de objetos alocados que antes da alocação malsucedida e agora é a hora de descartar as referências a objetos de tempo de execução para liberar ainda mais memória que pode ser necessária para limpeza. Nesses casos, pode até ser possível continuar, mas isso seria definitivamente uma má ideia, pois você nunca pode ter 100% de certeza de que a JVM está em um estado reparável.Demonstração que
OutOfMemoryError
não significa que a JVM está sem memória no bloco catch:Saída deste código:
Se estiver executando algo crítico, geralmente pego o
Error
, registro no syserr e, em seguida, registro usando minha estrutura de registro de escolha, e prossigo para liberar recursos e encerrar de forma limpa. Qual o pior que pode acontecer? A JVM está morrendo (ou já está morta) de qualquer maneira e, ao detectar o,Error
há pelo menos uma chance de limpeza.A ressalva é que você deve visar a detecção desses tipos de erros apenas em lugares onde a limpeza é possível. Não cubra tudo
catch(Throwable t) {}
ou um absurdo como esse.fonte
OutOfMemoryError
pode ter sido acionado de um ponto que colocou seu programa em um estado inconsistente, porque ele pode ser acionado a qualquer momento. Consulte stackoverflow.com/questions/8728866/…Você pode se recuperar dele:
Mas isso faz sentido? O que mais você gostaria de fazer? Por que você inicialmente alocaria tanta memória? Menos memória também está OK? Por que você ainda não faz uso dele? Ou, se isso não for possível, por que não apenas dar mais memória à JVM desde o início?
De volta às suas perguntas:
Nenhum vem à mente.
Depende do que causou o OOME. Se for declarado fora do
try
bloco e acontecer passo a passo, então suas chances são pequenas. Você pode querer reservar algum espaço de memória com antecedência:e, em seguida, defina-o para zero durante OOME:
É claro que isso não faz sentido;) Basta fornecer à JVM memória suficiente conforme sua aplicação requer. Execute um profiler, se necessário.
fonte
null
?Em geral, é uma má ideia tentar capturar e se recuperar de um OOM.
Um OOME também pode ter sido lançado em outros threads, incluindo threads que seu aplicativo nem conhece. Qualquer um desses threads agora estará morto e qualquer coisa que estava esperando por uma notificação pode ficar paralisada para sempre. Resumindo, seu aplicativo pode estar com problemas terminais.
Mesmo se a recuperação for bem-sucedida, sua JVM ainda pode estar sofrendo de falta de heap e seu aplicativo terá um desempenho péssimo como resultado.
A melhor coisa a fazer com um OOME é deixar o JVM morrer.
(Isso pressupõe que a JVM faz morrer. Por exemplo Ooms em um fio de servlet Tomcat não matar o JVM, e isso leva ao Tomcat entrar em um estado catatônico, onde ele não vai responder a todos os pedidos ... nem mesmo pede para reiniciar.)
EDITAR
Não estou dizendo que é uma má ideia pegar OOM. Os problemas surgem quando você tenta se recuperar do OOME, deliberadamente ou por descuido. Sempre que você capturar um OOM (diretamente ou como um subtipo de Erro ou Throwable), deverá lançá-lo novamente ou providenciar para que o aplicativo / JVM seja encerrado.
À parte: isso sugere que, para obter o máximo de robustez em face de OOMs, um aplicativo deve usar Thread.setDefaultUncaughtExceptionHandler () para definir um manipulador que fará com que o aplicativo saia no caso de um OOME, não importa em qual thread o OOME é lançado. Eu estaria interessado em opiniões sobre isso ...
O único outro cenário é quando você tem certeza de que o OOM não resultou em nenhum dano colateral; ou seja, você sabe:
Existem aplicativos onde é possível saber essas coisas, mas para a maioria dos aplicativos você não pode saber com certeza se a continuação após um OOME é segura. Mesmo que empiricamente "funcione" quando você tenta.
(O problema é que é necessária uma prova formal para mostrar que as consequências de OOMEs "antecipados" são seguras e que OOMEs "imprevistos" não podem ocorrer sob o controle de um OOME de try / catch.)
fonte
Error
.Sim, existem cenários do mundo real. Aqui está o meu: eu preciso processar conjuntos de dados de muitos itens em um cluster com memória limitada por nó. Uma determinada instância de JVM passa por muitos itens, um após o outro, mas alguns dos itens são muito grandes para processar no cluster: posso pegar o
OutOfMemoryError
e anotar quais itens são muito grandes. Posteriormente, posso executar novamente apenas os itens grandes em um computador com mais RAM.(Como é uma única alocação de vários gigabytes de uma matriz que falha, a JVM ainda está bem após detectar o erro e há memória suficiente para processar os outros itens.)
fonte
byte[] bytes = new byte[length]
? Por que não verificarsize
em um ponto anterior?size
vai ficar bem com mais memória. Eu passo pela exceção porque na maioria dos casos tudo vai ficar bem.Definitivamente, existem cenários em que capturar um OOME faz sentido. O IDEA os pega e abre uma caixa de diálogo para permitir que você altere as configurações de memória de inicialização (e sai quando terminar). Um servidor de aplicativos pode capturá-los e relatá-los. A chave para fazer isso é fazer isso em um alto nível no despacho para que você tenha uma chance razoável de ter um monte de recursos liberados no ponto em que está capturando a exceção.
Além do cenário IDEA acima, em geral a captura deve ser de Throwable, não apenas OOM especificamente, e deve ser feita em um contexto onde pelo menos o thread será encerrado em breve.
É claro que na maioria das vezes a memória passa fome e a situação não pode ser recuperada, mas existem maneiras de fazer sentido.
fonte
Eu me deparei com essa pergunta porque estava me perguntando se seria uma boa ideia capturar OutOfMemoryError no meu caso. Estou respondendo aqui parcialmente para mostrar outro exemplo em que detectar este erro pode fazer sentido para alguém (ou seja, eu) e parcialmente para descobrir se é uma boa ideia no meu caso (sendo eu um desenvolvedor super júnior, nunca poderei tenha certeza sobre qualquer linha de código que eu escrever).
De qualquer forma, estou trabalhando em um aplicativo Android que pode ser executado em diferentes dispositivos com diferentes tamanhos de memória. A parte perigosa é decodificar um bitmap de um arquivo e desdobrá-lo em uma instância de ImageView. Não quero restringir os dispositivos mais poderosos em termos de tamanho de bitmap decodificado, nem posso ter certeza de que o aplicativo não será executado em algum dispositivo antigo que nunca vi com memória muito baixa. Portanto, eu faço o seguinte:
Dessa forma, consigo prover dispositivos cada vez menos potentes de acordo com as necessidades e expectativas de seus usuários.
fonte
Sim, a verdadeira questão é "o que você vai fazer no manipulador de exceções?" Para quase tudo que for útil, você alocará mais memória. Se você gostaria de fazer algum trabalho de diagnóstico quando ocorre um OutOfMemoryError, você pode usar o
-XX:OnOutOfMemoryError=<cmd>
gancho fornecido pelo HotSpot VM. Ele executará seu (s) comando (s) quando ocorrer um OutOfMemoryError e você poderá fazer algo útil fora do heap do Java. Em primeiro lugar, você realmente deseja evitar que o aplicativo fique sem memória, portanto, descobrir por que isso acontece é a primeira etapa. Em seguida, você pode aumentar o tamanho de heap do MaxPermSize conforme apropriado. Aqui estão alguns outros ganchos de HotSpot úteis:Veja a lista completa aqui
fonte
OutOfMemeoryError
pode ser lançado em qualquer ponto de seu programa (não apenas a partir de umanew
instrução), seu programa estará em um estado indefinido quando você pegar a excção.Tenho um aplicativo que precisa se recuperar de falhas OutOfMemoryError e, em programas de thread único, sempre funciona, mas às vezes não funciona em programas de multi-thread. O aplicativo é uma ferramenta de teste Java automatizada que executa sequências de teste geradas com a profundidade máxima possível nas classes de teste. Agora, a IU deve ser estável, mas o mecanismo de teste pode ficar sem memória enquanto aumenta a árvore de casos de teste. Eu lido com isso pelo seguinte tipo de idioma de código no mecanismo de teste:
Isso sempre funciona em aplicativos de thread único. Mas recentemente coloquei meu mecanismo de teste em um thread de trabalho separado da IU. Agora, a falta de memória pode ocorrer arbitrariamente em qualquer segmento e não está claro para mim como capturá-la.
Por exemplo, fiz o OOME ocorrer enquanto os quadros de um GIF animado em minha IU estavam sendo alternados por um thread proprietário criado nos bastidores por uma classe Swing que está fora do meu controle. Eu pensei que tinha alocado todos os recursos necessários com antecedência, mas claramente o animador está alocando memória toda vez que busca a próxima imagem. Se alguém tiver uma ideia sobre como lidar com OOMEs levantados em qualquer tópico, eu adoraria ouvir.
fonte
OOME pode ser capturado, mas geralmente será inútil, dependendo se a JVM é capaz de coletar como lixo alguns objetos quando a captura é alcançada e de quanta memória de heap resta nesse momento.
Exemplo: na minha JVM, este programa é executado até a conclusão:
No entanto, apenas adicionar uma única linha na captura mostrará do que estou falando:
O primeiro programa funciona bem porque quando o catch é atingido, a JVM detecta que a lista não será mais usada (esta detecção também pode ser uma otimização feita em tempo de compilação). Portanto, quando alcançamos a instrução print, a memória heap foi liberada quase totalmente, portanto, agora temos uma ampla margem de manobra para continuar. Este é o melhor caso.
No entanto, se o código for organizado como a lista
ll
é usada após o OOME ter sido capturado, a JVM não será capaz de coletá-lo. Isso acontece no segundo trecho. O OOME, acionado por uma nova criação Longa, é capturado, mas logo estamos criando um novo Objeto (uma String naSystem.out,println
linha), e o heap está quase cheio, então um novo OOME é lançado. Este é o pior cenário: tentamos criar um novo objeto, falhamos, capturamos o OOME, sim, mas agora a primeira instrução que requer uma nova memória heap (por exemplo: criar um novo objeto) irá lançar um novo OOME. Pense nisso, o que mais podemos fazer neste momento com tão pouca memória sobrando ?. Provavelmente está saindo. Daí o inútil.Entre os motivos da JVM não coletar recursos, um é realmente assustador: um recurso compartilhado com outras threads também fazendo uso dele. Qualquer pessoa com cérebro pode ver como é perigoso capturar OOME se inserido em algum aplicativo não experimental de qualquer tipo.
Estou usando uma JVM (JRE6) do Windows x86 de 32 bits. A memória padrão para cada aplicativo Java é 64 MB.
fonte
ll=null
dentro do bloco de captura?A única razão pela qual posso pensar em detectar erros OOM pode ser que você tenha algumas estruturas de dados massivas que não está mais usando e pode definir como null e liberar memória. Mas (1) isso significa que você está perdendo memória e deve corrigir seu código em vez de apenas mancar após um OOME, e (2) mesmo se você o detectasse, o que faria? OOM pode acontecer a qualquer momento, potencialmente deixando tudo pela metade.
fonte
Para a questão 2 já vejo a solução que sugeriria, da BalusC.
Acho que acabei de encontrar um bom exemplo. Quando o aplicativo awt está despachando mensagens, o OutOfMemoryError não bloqueado é exibido em stderr e o processamento da mensagem atual é interrompido. Mas o aplicativo continua funcionando! O usuário ainda pode emitir outros comandos sem saber dos problemas graves que acontecem nos bastidores. Principalmente quando ele não consegue ou não observa o erro padrão. Portanto, capturar a exceção oom e fornecer (ou pelo menos sugerir) a reinicialização do aplicativo é algo desejado.
fonte
Eu apenas tenho um cenário em que capturar um OutOfMemoryError parece fazer sentido e parece funcionar.
Cenário: em um aplicativo Android, desejo exibir vários bitmaps na resolução mais alta possível e quero poder ampliá-los com fluência.
Por causa do zoom fluente, quero ter os bitmaps na memória. No entanto, o Android tem limitações de memória que dependem do dispositivo e são difíceis de controlar.
Nessa situação, pode haver OutOfMemoryError ao ler o bitmap. Aqui, ajuda se eu pegar e continuar com uma resolução mais baixa.
fonte
OutOfMemory
não acontece devido a uma correção não relacionada). No entanto, mesmo se você pegá-lo, ele ainda pode ter quebrado algum código importante: se você tiver vários threads, a alocação de memória pode falhar em qualquer um deles. Portanto, dependendo do seu aplicativo, ainda há de 10 a 90% de chance de ele ser irreversivelmente quebrado.EDIT: Eu sugiro que você experimente. Digamos, escreva um programa que chame recursivamente uma função que aloque progressivamente mais memória. Pegue
OutOfMemoryError
e veja se você pode continuar a partir desse ponto de forma significativa. De acordo com minha experiência, você conseguirá, embora no meu caso tenha acontecido no servidor WebLogic, então pode ter havido alguma magia negra envolvida.fonte
Você pode capturar qualquer coisa em Throwable; de modo geral, você só deve capturar subclasses de Exception, exceto RuntimeException (embora uma grande parte dos desenvolvedores também capture RuntimeException ... mas essa nunca foi a intenção dos designers de linguagem).
Se você pegasse OutOfMemoryError, o que diabos você faria? A VM está sem memória, basicamente tudo que você pode fazer é sair. Você provavelmente nem consegue abrir uma caixa de diálogo para dizer a eles que está sem memória, pois isso consumiria memória :-)
A VM lança um OutOfMemoryError quando está realmente sem memória (na verdade, todos os erros devem indicar situações irrecuperáveis) e não deve haver nada que você possa fazer para lidar com isso.
As coisas a fazer são descobrir por que você está ficando sem memória (use um criador de perfil, como o do NetBeans) e certifique-se de que não haja vazamentos de memória. Se você não tiver vazamentos de memória, aumente a memória que você aloca para a VM.
fonte