Estou ciente de que todo objeto requer memória de pilha e toda referência / primitivo na pilha requer memória de pilha.
Quando tento criar um objeto no heap e não há memória suficiente para fazer isso, a JVM cria um java.lang.OutOfMemoryError no heap e o lança para mim.
Então, implicitamente, isso significa que há alguma memória reservada pela JVM na inicialização.
O que acontece quando essa memória reservada é usada (definitivamente seria usada, leia a discussão abaixo) e a JVM não possui memória suficiente no heap para criar uma instância de java.lang.OutOfMemoryError ?
Isso apenas trava? Ou ele me daria uma null
vez que não há memória para new
uma instância do OOM?
try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}
==
Por que a JVM não pôde reservar memória suficiente?
Independentemente da quantidade de memória reservada, ainda é possível que essa memória seja usada se a JVM não tiver uma maneira de "recuperar" essa memória:
try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}
Ou, mais concisamente:
private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}
fonte
OutOfMemoryException
e, em seguida, fazer algo que envolveu a criação de um buffer grande ...OutOfMemoryError
e mantivesse uma referência a ela. Parece que pegar umOutOfMemoryError
não é tão útil quanto se poderia pensar, porque você pode garantir quase nada sobre o estado do seu programa ao pegá-lo. Veja stackoverflow.com/questions/8728866/…Respostas:
A JVM realmente nunca fica sem memória. Faz o cálculo de memória da pilha de heap com antecedência.
A estrutura da JVM, capítulo 3 , seção 3.5.2, afirma:
Para Heap , Seção 3.5.3.
Portanto, ele faz um cálculo antecipado antes de fazer a alocação do objeto.
O que acontece é que a JVM tenta alocar memória para um objeto na memória chamado região de geração permanente (ou PermSpace). Se a alocação falhar (mesmo após a JVM chamar o Garbage Collector para tentar alocar espaço livre), ele emitirá um
OutOfMemoryError
. Mesmo as exceções requerem um espaço de memória para que o erro seja gerado indefinidamente.Leitura adicional. ? Além disso,
OutOfMemoryError
pode ocorrer em diferentes estruturas da JVM.fonte
Graham Borland parece estar certo : pelo menos minha JVM aparentemente reutiliza OutOfMemoryErrors. Para testar isso, escrevi um programa de teste simples:
A execução produz esta saída:
BTW, a JVM que estou executando (no Ubuntu 10.04) é esta:
Edit: Tentei ver o que aconteceria se eu obrigasse a JVM a ficar completamente sem memória usando o seguinte programa:
Como se vê, parece fazer um loop para sempre. No entanto, curiosamente, tentar finalizar o programa com Ctrl+ Cnão funciona, mas apenas fornece a seguinte mensagem:
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated
fonte
n
OOMs e reutilizar um deles posteriormente, para que um OOM sempre possa ser lançado. A JVM da Sun / Oracle não suporta recursão de cauda em todo o IIRC?n
quadros na pilha e acaba criando e destruindo o quadron+1
por toda a eternidade, dando a aparência de funcionar sem parar.A maioria dos ambientes de tempo de execução pré-aloca na inicialização ou reserva memória suficiente para lidar com situações de falta de memória. Eu imagino que a maioria das implementações sãs da JVM faria isso.
fonte
catch
cláusula tente usar mais memória, a JVM poderá continuar lançando a mesma instância do OOM repetidamente.Na última vez em que estava trabalhando em Java e usando um depurador, o inspetor de heap mostrou que a JVM alocou uma instância de OutOfMemoryError na inicialização. Em outras palavras, ele aloca o objeto antes que seu programa possa começar a consumir, e muito menos ficar sem memória.
fonte
Na especificação da JVM, capítulo 3.5.2:
Toda Java Virtual Machine tem que garantir que lançará um
OutOfMemoryError
. Isso implica que ele deve ser capaz de criar uma instânciaOutOfMemoryError
(ou ter que ser criada com antecedência), mesmo que não haja mais espaço de pilha.Embora não seja necessário garantir, resta memória suficiente para capturá-lo e imprimir um bom rastreamento de pilha ...
Adição
Você adicionou algum código para mostrar, que a JVM pode ficar sem espaço de heap se precisar lançar mais de um
OutOfMemoryError
. Mas essa implementação violaria o requisito acima.Não é necessário que as instâncias lançadas
OutOfMemoryError
sejam únicas ou criadas sob demanda. Uma JVM pode preparar exatamente uma instância deOutOfMemoryError
durante a inicialização e executá-la sempre que ficar sem espaço de heap - que é uma vez, no ambiente normal. Em outras palavras: a instância doOutOfMemoryError
que vemos pode ser um singleton.fonte
Pergunta interessante :-). Enquanto os outros deram boas explicações sobre os aspectos teóricos, decidi experimentá-lo. Isso está no Oracle JDK 1.6.0_26, Windows 7 de 64 bits.
Configuração de teste
Eu escrevi um programa simples para esgotar a memória (veja abaixo).
O programa apenas cria uma estática
java.util.List
e continua colocando novas seqüências nela, até que o OOM seja acionado. Em seguida, ele o captura e continua a acumular em um loop sem fim (pobre JVM ...).Resultado do teste
Como se pode ver pela saída, as quatro primeiras vezes que o OOME é lançado, ele vem com um rastreamento de pilha. Depois disso, os OOMEs subsequentes são impressos apenas
java.lang.OutOfMemoryError: Java heap space
seprintStackTrace()
forem invocados.Então, aparentemente, a JVM faz um esforço para imprimir um rastreamento de pilha, se puder, mas se a memória estiver realmente fraca, ela simplesmente omite o rastreamento, exatamente como as outras respostas sugerem.
Também interessante é o código de hash do OOME. Observe que os primeiros OOME têm hashes diferentes. Depois que a JVM começa a omitir rastreamentos de pilha, o hash é sempre o mesmo. Isso sugere que a JVM usará instâncias OOME novas (pré-alocadas?) O maior tempo possível, mas, se for necessário pressionar, apenas reutilizará a mesma instância em vez de não ter nada para lançar.
Resultado
Nota: Truncei alguns rastreios de pilha para facilitar a leitura da saída ("[...]").
O programa
fonte
System.out
masprintStackTrace()
usaSystem.err
por padrão. Você provavelmente obteria melhores resultados usando qualquer fluxo de forma consistente.Tenho certeza de que a JVM garantirá que tenha pelo menos memória suficiente para lançar uma exceção antes de ficar sem memória.
fonte
Exceções indicando uma tentativa de violar os limites de um ambiente de memória gerenciada são tratadas pelo tempo de execução do referido ambiente, neste caso a JVM. A JVM é seu próprio processo, que está executando a IL do seu aplicativo. Se um programa tentar fazer uma chamada que estenda a pilha de chamadas além dos limites ou alocar mais memória do que a JVM pode reservar, o próprio tempo de execução injetará uma exceção, o que fará com que a pilha de chamadas seja desenrolada. Independentemente da quantidade de memória que seu programa precisa atualmente ou da profundidade de sua pilha de chamadas, a JVM terá alocado memória suficiente dentro de seus próprios limites do processo para criar a exceção e injetar em seu código.
fonte
Você parece estar confundindo a memória virtual reservada pela JVM na qual a JVM executa programas Java com a memória nativa do SO host na qual a JVM é executada como um processo nativo. A JVM em sua máquina está sendo executada na memória gerenciada pelo sistema operacional, não na memória que a JVM reservou para executar programas Java.
Leitura adicional:
E, como observação final, tentar capturar um java.lang.Error (e suas classes descendentes) para imprimir um rastreamento de pilha pode não fornecer informações úteis. Você deseja um despejo de pilha.
fonte
Para esclarecer melhor a resposta de @Graham Borland, funcionalmente, a JVM faz isso na inicialização:
Posteriormente, a JVM executa um dos seguintes bytecodes Java: 'new', 'anewarray' ou 'multianewarray'. Esta instrução faz com que a JVM execute várias etapas em uma condição de falta de memória:
allocate()
.allocate()
tenta alocar memória para alguns uma nova instância de uma classe ou matriz específica.doGC()
, que tenta fazer a coleta de lixo.allocate()
tenta alocar memória para a instância novamente.throw OOME;
assignate (), simplesmente executa a , referindo-se ao OOME que foi instanciado na inicialização. Observe que ele não precisava alocar esse OOME, apenas se refere a ele.Obviamente, essas não são etapas literais; eles variam de JVM para JVM na implementação, mas essa é a ideia de alto nível.
(*) Uma quantidade significativa de trabalho acontece aqui antes de falhar. A JVM tentará limpar objetos do SoftReference, tentará alocar diretamente na geração ocupada ao usar um coletor de gerações e possivelmente outras coisas, como finalização.
fonte
As respostas que dizem que a JVM será pré-alocada
OutOfMemoryErrors
estão realmente corretas.Além de testar isso provocando uma situação de falta de memória, podemos apenas verificar a pilha de qualquer JVM (usei um pequeno programa que apenas dorme, executando-o usando o Hotspot JVM da Oracle a partir do Java 8, atualização 31).
Usando
jmap
, vemos que parece haver 9 instâncias de OutOfMemoryError (embora tenhamos muita memória):Em seguida, podemos gerar um dump de heap:
e abra-o usando o Eclipse Memory Analyzer , onde uma consulta OQL mostra que a JVM realmente parece pré-alocada
OutOfMemoryErrors
para todas as mensagens possíveis:O código da JVM do Java 8 Hotspot que realmente os pré-aloca pode ser encontrado aqui e se parece com este (com algumas partes omitidas):
e esse código mostra que a JVM primeiro tentará usar um dos erros pré-alocados com espaço para um rastreamento de pilha e, em seguida, retornará a um sem rastreamento de pilha:
fonte
Metaspace
. Seria bom se você pudesse mostrar um pedaço de código que também atende ao PermGen e o compare com o Metaspace.