O que acontece quando há memória insuficiente para gerar um OutOfMemoryError?

207

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 nullvez que não há memória para newuma 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);
    }
}
Pacerier
fonte
2
sua resposta seria JVM dependente, em grande medida
MozenRath 13/02/12
23
Uma biblioteca de telefonia, uma vez que eu usei (nos anos 90) usado para pegar o OutOfMemoryExceptione, em seguida, fazer algo que envolveu a criação de um buffer grande ...
Tom Hawtin - tackline
@ TomHawtin-tackline E se as operações envolvidas nisso fizerem outro OOM?
Pacerier 13/02/12
38
Como um celular, ele fica sem bateria, mas tem a bateria suficiente para continuar enviando spam "Você está ficando sem bateria".
Kazuma
1
"O que acontece quando essa memória reservada é consumida": isso poderia acontecer apenas se o programa capturasse a primeira OutOfMemoryErrore mantivesse uma referência a ela. Parece que pegar um OutOfMemoryErrornã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/…
Raedwald

Respostas:

145

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:

  • Se as pilhas da máquina virtual Java puderem ser expandidas dinamicamente e a tentativa de expansão, mas a memória insuficiente puder ser disponibilizada para efetivar a expansão, ou se a memória insuficiente estiver disponível para criar a pilha inicial da máquina virtual Java para um novo encadeamento, o Java virtual máquina lança um OutOfMemoryError.

Para Heap , Seção 3.5.3.

  • Se um cálculo exigir mais heap do que o sistema de gerenciamento de armazenamento automático pode ser disponibilizado, a máquina virtual Java lançará um OutOfMemoryError.

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, OutOfMemoryErrorpode ocorrer em diferentes estruturas da JVM.

Buhake Sindi
fonte
10
Quero dizer sim, mas a máquina virtual Java também não precisaria de memória para lançar um OutOfMemoryError? O que acontece quando não há memória para lançar um OOM?
Pacerier
5
Mas se a JVM não retornar a referência para a mesma instância de uma OOM, você não concorda que eventualmente ela acabe com a memória reservada? (tal como demonstrado no código em questão)
Pacerier
1
Permita-me colocar uma referência ao comentário de Graham aqui: stackoverflow.com/questions/9261705/…
Pacerier
2
Pode ser bom se a VM reteve um singleton de OOM Exception para o caso extremo declarado e em uma usina nuclear.
John K
8
@ JohnK: Eu espero que as usinas nucleares não sejam programadas em Java, assim como os ônibus espaciais e os Boeing 757s não sejam programados em Java.
Dietrich Epp
64

Graham Borland parece estar certo : pelo menos minha JVM aparentemente reutiliza OutOfMemoryErrors. Para testar isso, escrevi um programa de teste simples:

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

A execução produz esta saída:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

BTW, a JVM que estou executando (no Ubuntu 10.04) é esta:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

Edit: Tentei ver o que aconteceria se eu obrigasse a JVM a ficar completamente sem memória usando o seguinte programa:

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

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

Ilmari Karonen
fonte
Teste agradável, mesmo para mim com a versão "1.7.0_01" Java HotSpot (TM) 64-Bit Servidor VM
Pacerier
Interessante. Isso faz parecer que a JVM não entende completamente cauda recursão ... (como se fosse fazer alguma reutilização pilha cauda-recursivo, mas não o bastante para limpar toda a memória ...)
Izkata
Modifiquei seu código para ver quantos códigos diferentes eu recebo - eu sempre executo o ramo else exatamente 5 vezes.
Irfy
@ Izkata: Prefiro dizer que é uma decisão consciente pré-alocar nOOMs 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?
Irfy 13/02/12
10
@Izkata O loop está executando aparentemente interminavelmente porque a JVM está constantemente lançando um e o mesmo (quinto ou mais) OOM após ficar sem memória. Por isso, possui nquadros na pilha e acaba criando e destruindo o quadro n+1por toda a eternidade, dando a aparência de funcionar sem parar.
Irfy
41

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.

Graham Borland
fonte
1
stackoverflow.com/questions/9261215/… : Verdadeiro, mas se a JVM reservou 100 unidades de memória para isso e gastou 1 unidade na primeira OOM, o que acontece se no bloco de captura do OOM eu fizer e.printStackTrace ()? O e.printStackTrace () também requer memória. Em seguida, a JVM gastaria outra unidade de memória para me lançar outra OOM (98 unidades restantes) e eu a pego com um e.printStackTrace (), para que a JVM me lance outra OOM (97 unidades restantes) e bem, isso é capturado e eu queria ..
Pacerier
3
É exatamente por isso que o OOME nunca costumava incluir um rastreamento de pilha - os rastreamentos de pilha levam memória! O OOME começou a tentar incluir um rastreamento de pilha no java 6 ( blogs.oracle.com/alanb/entry/… ). Presumo que, se o rastreamento de pilha não for possível, a exceção será lançada sem um rastreamento de pilha.
21712 Sean Sean-Reilly
1
@SeanReilly Quero dizer que uma exceção que não possui um rastreamento de pilha ainda é um Objeto, que ainda requer memória. É necessária memória, independentemente de um rastreamento de pilha ser fornecido. É verdade que, no bloco catch, se não houver memória restante para criar um OOM (nenhuma memória para criar nem um sem rastreamento de pilha), então eu pegaria um nulo?
Pacerier 13/02/12
17
A JVM pode retornar várias referências a uma única instância estática da exceção OOM. Portanto, mesmo que sua catchcláusula tente usar mais memória, a JVM poderá continuar lançando a mesma instância do OOM repetidamente.
Graham Borland
1
@TheEliteGentleman Concordo que essas também são respostas muito boas, mas a JVM vive em uma máquina física. Essas respostas não explicaram como a JVM poderia magicamente ter memória suficiente para sempre fornecer uma instância de OOM. "É sempre a mesma instância" parece resolver o enigma.
Pacerier 13/02/12
23

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.

benzado
fonte
12

Na especificação da JVM, capítulo 3.5.2:

Se as pilhas da Java virtual machine puderem ser expandidas dinamicamente e a tentativa de expansão, mas a memória insuficiente puder ser disponibilizada para efetivar a expansão, ou se a memória insuficiente puder ser disponibilizada para criar a pilha inicial da Java virtual machine para um novo encadeamento, o Java virtual máquina lança umOutOfMemoryError .

Toda Java Virtual Machine tem que garantir que lançará um OutOfMemoryError. Isso implica que ele deve ser capaz de criar uma instância OutOfMemoryError(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 OutOfMemoryErrorsejam únicas ou criadas sob demanda. Uma JVM pode preparar exatamente uma instância de OutOfMemoryErrordurante 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 do OutOfMemoryErrorque vemos pode ser um singleton.

Andreas Dolk
fonte
Eu acho que para implementar isso, teria que se abster de registrar o rastreamento de pilha, se o espaço fosse pequeno.
Raedwald 15/02/12
@Raedwald: De fato, é exatamente isso que a Oracle VM faz, veja minha resposta.
sleske
11

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.Liste 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 spacese printStackTrace()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 ("[...]").

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

O programa

public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}
sleske
fonte
Quando o push é acionado, ele não pode alocar memória para o OOME, portanto libera os que já foram criados.
Buhake Sindi
1
Nota secundária: parte de sua saída parece estar fora de sequência, provavelmente porque você está imprimindo, System.outmas printStackTrace()usa System.errpor padrão. Você provavelmente obteria melhores resultados usando qualquer fluxo de forma consistente.
Ilmari Karonen
@IlmariKaronen: Sim, eu notei isso. É apenas um exemplo, então não importava. Obviamente você não o usaria no código de produção.
sleske
É verdade que demorei um pouco para descobrir o que estava acontecendo quando olhei pela primeira vez para a saída.
Ilmari Karonen
6

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.

Oscar Gomez
fonte
1
Mas eles iriam acertar esta situação não importa o quanto de memória é reservado: stackoverflow.com/questions/9261705/...
Pacerier
4

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.

KeithS
fonte
"a JVM terá alocado memória suficiente dentro de seus próprios limites do processo para criar a referida exceção", mas se o seu código reter uma referência a essa exceção, para que não possa ser reutilizado, como pode criar outra? Ou você está sugerindo que ele tenha um objeto OOME Singleton especial?
Raedwald 15/02/12
Eu não estou sugerindo isso. Se o seu programa interceptar e se prender a todas as exceções já criadas, incluindo aquelas criadas e injetadas pela JVM ou OS, eventualmente a própria JVM excederá alguns limites definidos pelo sistema operacional e o sistema operacional a desativará para um GPF ou erro semelhante. No entanto, isso é um design ruim em primeiro lugar; as exceções devem ser tratadas e, em seguida, ficar fora do escopo ou serem descartadas. E você NUNCA deve tentar capturar e continuar em uma SOE ou OOME; além de "limpar" para que você possa sair normalmente, não há nada que você possa fazer para continuar a execução nessas situações.
21412 KeithS
"esse é um design ruim": design terrível. Mas pedanticamente, a JVM estaria em conformidade com a especificação se falhasse dessa maneira?
Raedwald 15/02/12
4

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.

Michael Tiffany
fonte
4

Para esclarecer melhor a resposta de @Graham Borland, funcionalmente, a JVM faz isso na inicialização:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

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:

  1. Invoque uma função nativa, digamos allocate(). allocate()tenta alocar memória para alguns uma nova instância de uma classe ou matriz específica.
  2. Como a solicitação de alocação falha, a JVM chama outra função nativa, por exemplo doGC(), que tenta fazer a coleta de lixo.
  3. Quando essa função retorna, allocate()tenta alocar memória para a instância novamente.
  4. Se isso falhar (*), a JVM, dentro de 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.

ahawtho
fonte
3

As respostas que dizem que a JVM será pré-alocada OutOfMemoryErrorsestã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):

> jmap -histo 12103 | grep OutOfMemoryError
 71: 9 288 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

Em seguida, podemos gerar um dump de heap:

> jmap -dump: format = b, arquivo = heap.hprof 12315

e abra-o usando o Eclipse Memory Analyzer , onde uma consulta OQL mostra que a JVM realmente parece pré-alocada OutOfMemoryErrorspara todas as mensagens possíveis:

insira a descrição da imagem aqui

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):

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg());

  msg = java_lang_String::create_from_str("Metaspace", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = java_lang_String::create_from_str("Compressed class space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = java_lang_String::create_from_str("/ by zero", CHECK_false);
  java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_java_heap->klass();
  assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

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:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}
Johan Kaving
fonte
Boa postagem, aceitarei isso como resposta por uma semana para dar mais visibilidade antes de voltar à resposta anterior.
Pacerier 24/05
No Java 8 e superior, o Permanent Generation Space foi removido completamente e não é mais possível especificar o tamanho da memória de alocação de espaço de heap do Java 8 e superior, pois eles introduziram o gerenciamento de memória de metadados de classe dinâmica chamado 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.
Buhake Sindi
@BuhakeSindi - Não vejo o que a Geração Permanente tem a ver com isso. Novos objetos não são alocados na Geração Permanente, como você declara em sua resposta. Você também nunca menciona o fato de que OutOfMemoryErrors são pré-alocados (que é a resposta real à pergunta).
Johan Kaving
1
Ok, o que estou dizendo é que, no Java 8, a alocação de objetos é calculada dinamicamente, enquanto antes era alocada em um espaço de pilha predefinido, portanto, talvez seja pré-alocada previamente. Mesmo que o OOME seja pré-alocado, há uma "computação" feita para determinar se o OOME precisa ser lançado (daí, por que eu coloquei referência à especificação JLS).
Buhake Sindi
1
O tamanho do heap Java é tão predefinido no Java 8 quanto antes. A Geração Permanente fazia parte da pilha que continha metadados de classe, seqüências de caracteres internas e estática de classe. Ele tinha um tamanho limitado que precisava ser ajustado separadamente do tamanho total da pilha. No Java 8, os metadados da classe foram movidos para a memória nativa e as seqüências internas e as estáticas da classe foram movidas para o heap Java regular (veja, por exemplo, aqui: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving