Cosa succede quando c'è memoria insufficiente per lanciare un OutOfMemoryError?


207

Sono consapevole che ogni oggetto richiede memoria heap e ogni primitiva / riferimento nello stack richiede memoria stack.

Quando provo a creare un oggetto nell'heap e non c'è memoria sufficiente per farlo, la JVM crea un java.lang.OutOfMemoryError sull'heap e me lo lancia.

Quindi implicitamente, ciò significa che c'è un po 'di memoria riservata da JVM all'avvio.

Cosa succede quando questa memoria riservata è esaurita (sarebbe sicuramente esaurita, leggi la discussione sotto) e la JVM non ha abbastanza memoria sull'heap per creare un'istanza di java.lang.OutOfMemoryError ?

Si blocca e basta? O mi lancerebbe un nulldato che non c'è memoria per newun'istanza di 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
}

==

Perché la JVM non ha potuto riservare memoria sufficiente?

Indipendentemente dalla quantità di memoria riservata, è comunque possibile che quella memoria venga utilizzata se la JVM non ha un modo per "recuperare" quella memoria:

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
                    }
                }
            }
        }
    }
}

O più concisamente:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

2
la tua risposta dipende in larga misura dalla JVM
MozenRath,

23
Una libreria di telefonia che una volta usavo (negli anni '90) era solita catturare OutOfMemoryExceptione poi fare qualcosa che comportava la creazione di un grosso buffer ...
Tom Hawtin - tackline

@ TomHawtin-tackline Cosa succede se le operazioni coinvolte nel fare ciò generano un'altra OOM?
Pacerier,

38
Come un cellulare, si sta esaurendo la batteria ma ha la batteria sufficiente per mantenere lo spamming "La batteria si sta esaurendo".
Kazuma,

1
"Cosa succede quando questa memoria riservata è esaurita": ciò potrebbe accadere solo se il programma catturasse il primo OutOfMemoryErrore conservasse un riferimento ad esso. Traspare che catturare un OutOfMemoryErrornon sia così utile come si potrebbe pensare, perché non si può garantire quasi nulla sullo stato del programma nel catturarlo. Vedi stackoverflow.com/questions/8728866/…
Raedwald

Risposte:


145

La JVM non esaurisce mai davvero la memoria. Esegue in anticipo il calcolo della memoria dello stack di heap.

La struttura della JVM, capitolo 3 , sezione 3.5.2 afferma:

  • Se gli stack di macchine virtuali Java possono essere espansi in modo dinamico e viene tentata l'espansione ma è possibile rendere disponibile memoria insufficiente per effettuare l'espansione o se è possibile rendere disponibile memoria insufficiente per creare lo stack iniziale di macchine virtuali Java per un nuovo thread, Java virtual la macchina lancia un OutOfMemoryError.

Per Mucchio , Sezione 3.5.3.

  • Se un calcolo richiede più heap di quanto possa essere reso disponibile dal sistema di gestione della memorizzazione automatica, la macchina virtuale Java genera un OutOfMemoryError.

Quindi, esegue un calcolo in anticipo prima di eseguire l'allocazione dell'oggetto.


Quello che succede è che la JVM tenta di allocare memoria per un oggetto nella memoria chiamato regione di generazione permanente (o PermSpace). Se l'allocazione non riesce (anche dopo che JVM ha richiamato Garbage Collector per provare ad allocare spazio libero), genera un OutOfMemoryError. Anche le eccezioni richiedono uno spazio di memoria, quindi l'errore verrà generato indefinitamente.

Ulteriori letture ? Inoltre, OutOfMemoryErrorpuò verificarsi in diverse strutture JVM.


10
Voglio dire sì, ma la macchina virtuale Java non avrebbe anche bisogno di memoria per lanciare un OutOfMemoryError? Cosa succede quando non c'è memoria per lanciare una OOM?
Pacerier,

5
Ma se la JVM non restituisce il riferimento alla stessa istanza di una OOM, non sei d'accordo che alla fine si esaurirebbe la sua memoria riservata? (come dimostrato nel codice nella domanda)
Pacerier,

1
Mi permetta di mettere un riferimento al commento di Graham qui: stackoverflow.com/questions/9261705/...
Pacerier

2
Sarebbe bello se la VM conservasse un singleton di OOM Exception per il caso estremo dichiarato e in una centrale nucleare.
John K,

8
@JohnK: Spero che le centrali nucleari non siano programmate in Java, proprio come le navette spaziali e il Boeing 757 non sono programmate in Java.
Dietrich Epp,

64

Graham Borland sembra aver ragione : almeno la mia JVM sembra riutilizzare OutOfMemoryErrors. Per provare questo, ho scritto un semplice programma di test:

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);
    }
}

Eseguendolo produce questo output:

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

A proposito, la JVM che sto eseguendo (su Ubuntu 10.04) è questa:

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

Modifica: ho provato a vedere cosa sarebbe successo se avessi costretto la JVM a esaurire completamente la memoria usando il seguente programma:

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);
    }
}

A quanto pare, sembra in loop per sempre. Tuttavia, curiosamente, provare a terminare il programma con Ctrl+ Cnon funziona, ma dà solo il seguente messaggio:

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


Bel test, lo stesso per me con la versione "1.7.0_01" Java HotSpot (TM) VM a 64 bit Server
Pacerier

Interessante. Ciò fa sembrare che la JVM non capisca completamente la ricorsione della coda ... (Come se
stesse

Modificato il tuo codice per vedere quanti diversi ottengo: ottengo sempre il ramo else eseguito esattamente 5 volte.
Irfy,

@Izkata: Preferirei dire che è una decisione consapevole di pre-allocare nOOM e riutilizzarne una in seguito, in modo che una OOM possa sempre essere lanciata. La JVM di Sun / Oracle non supporta la ricorsione della coda in tutti gli IIRC?
Irfy,

10
@Izkata Il loop sta funzionando apparentemente all'infinito perché la JVM lancia costantemente la stessa OOM (quinta o giù di lì) dopo che ha esaurito la memoria. Quindi ha nframe in pila e finisce per creare e distruggere frame n+1per l'eternità, dando l'impressione di correre all'infinito.
Irfy,

41

La maggior parte degli ambienti di runtime si pre-allocano all'avvio, o altrimenti riservano, memoria sufficiente per gestire le situazioni di inedia della memoria. Immagino che la maggior parte delle implementazioni JVM sensate farebbero questo.


1
stackoverflow.com/questions/9261215/… : Vero ma se la JVM ha riservato 100 unità di memoria per farlo, e ha speso 1 unità sulla prima OOM, cosa succede se nel mio blocco catch OOM faccio e.printStackTrace ()? e.printStackTrace () richiede anche memoria. Quindi la JVM spenderebbe un'altra unità di memoria per lanciarmi un'altra OOM (98 unità rimaste) e la prendo con un e.printStackTrace () così la JVM mi lancia un'altra OOM (97 unità rimaste) e bene che viene catturato e volevo ..
Pacerier,

3
Questo è esattamente il motivo per cui OOME non ha mai usato per includere una traccia dello stack - le tracce dello stack prendono memoria! OOME ha iniziato a provare a includere una traccia dello stack in Java 6 ( blogs.oracle.com/alanb/entry/… ). Suppongo che se la traccia dello stack non è possibile, l'eccezione viene generata senza una traccia dello stack.
Sean Reilly,

1
@SeanReilly Intendo un'eccezione che non ha una traccia di stack è ancora un oggetto, che richiede ancora memoria. La memoria è necessaria indipendentemente dal fatto che sia fornita una traccia dello stack. È vero che nel blocco catch se non è rimasta memoria per creare una OOM (nessuna memoria per crearne una senza traccia stack), allora catturerei un null?
Pacerier,

17
La JVM potrebbe restituire più riferimenti a una singola istanza statica dell'eccezione OOM. Quindi, anche se la tua catchclausola tenta di utilizzare più memoria, JVM può continuare a lanciare ripetutamente la stessa istanza OOM.
Graham Borland,

1
@TheEliteGentleman Concordo sul fatto che anche quelle siano risposte molto valide, ma la JVM vive su una macchina fisica, quelle risposte non spiegano come la JVM potrebbe magicamente avere memoria sufficiente per fornire sempre un'istanza di OOM. "È sempre la stessa istanza" sembra risolvere l'enigma.
Pacerier,

23

L'ultima volta che stavo lavorando in Java e utilizzando un debugger, l'ispettore heap ha mostrato che la JVM ha allocato un'istanza di OutOfMemoryError all'avvio. In altre parole, alloca l'oggetto prima che il programma abbia la possibilità di iniziare a consumare, e tanto meno esaurire, la memoria.


12

Dalle specifiche JVM, capitolo 3.5.2:

Se gli stack di macchine virtuali Java possono essere espansi in modo dinamico e viene tentata l'espansione ma è possibile rendere disponibile memoria insufficiente per effettuare l'espansione o se è possibile rendere disponibile memoria insufficiente per creare lo stack iniziale di macchine virtuali Java per un nuovo thread, Java virtual la macchina lancia unOutOfMemoryError .

Ogni Java Virtual Machine deve garantire che lancerà un OutOfMemoryError. Ciò implica che deve essere in grado di creare un'istanza di OutOfMemoryError(o di averla creata in anticipo) anche se non c'è più spazio sul mucchio.

Anche se non è necessario garantire che rimanga memoria sufficiente per catturarlo e stampare una bella traccia stack ...

aggiunta

Hai aggiunto del codice per mostrare che la JVM potrebbe esaurire lo spazio heap se dovesse lanciarne più di uno OutOfMemoryError. Ma una tale implementazione violerebbe il requisito dall'alto.

Non è necessario che le istanze generate OutOfMemoryErrorsiano univoche o create su richiesta. Una JVM potrebbe preparare esattamente un'istanza di OutOfMemoryErrordurante l'avvio e lanciarla ogni volta che si esaurisce lo spazio dell'heap - che è una volta, in un ambiente normale. In altre parole: l'istanza OutOfMemoryErrorche vediamo potrebbe essere un singleton.


Immagino che per implementare questo dovrebbe astenersi dal registrare lo stack-trace, se lo spazio fosse limitato.
Raedwald,

@Raedwald: In effetti, questo è esattamente ciò che fa la VM Oracle, vedi la mia risposta.
sleske,

11

Domanda interessante :-). Mentre gli altri hanno dato buone spiegazioni degli aspetti teorici, ho deciso di provarlo. Questo è su Oracle JDK 1.6.0_26, Windows 7 a 64 bit.

Configurazione di prova

Ho scritto un semplice programma per esaurire la memoria (vedi sotto).

Il programma crea solo una statica java.util.Liste continua a riempire stringhe fresche fino a quando non viene generata OOM. Quindi lo cattura e continua a riempirsi in un ciclo infinito (povero JVM ...).

Risultato del test

Come si può vedere dall'output, le prime quattro volte che viene lanciato OOME, viene fornito con una traccia dello stack. Successivamente, gli OOME successivi vengono stampati solo java.lang.OutOfMemoryError: Java heap spacese printStackTrace()viene invocato.

Quindi, a quanto pare, la JVM fa uno sforzo per stampare una traccia dello stack, se può, ma se la memoria è davvero stretta, omette la traccia, proprio come suggeriscono le altre risposte.

Interessante anche il codice hash di OOME. Nota che i primi OOME hanno tutti hash diversi. Una volta che JVM inizia a omettere le tracce dello stack, l'hash è sempre lo stesso. Ciò suggerisce che la JVM utilizzerà istanze OOME nuove (preallocate?) Il più a lungo possibile, ma se la spinta arriva, verrà semplicemente riutilizzata la stessa istanza anziché non avere nulla da lanciare.

Produzione

Nota: ho troncato alcune tracce dello stack per facilitare la lettura dell'output ("[...]").

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...
[...]

Il programma

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);
            }
        }

    }
}

Quando arriva il push, non può allocare memoria per OOME, quindi scarica quelli già creati.
Buhake Sindi,

1
Nota minore: alcuni dei tuoi output sembrano essere fuori sequenza, presumibilmente perché stai stampando System.outma li printStackTrace()usi System.errdi default. Probabilmente otterrai risultati migliori usando entrambi i flussi in modo coerente.
Ilmari Karonen,

@IlmariKaronen: Sì, l'ho notato. È solo un esempio, quindi non importava. Ovviamente non lo useresti nel codice di produzione.
sleske,

È vero, mi ci è voluto solo un po 'per capire cosa stesse succedendo il **** quando ho guardato per la prima volta l'output.
Ilmari Karonen,


4

Le eccezioni che indicano un tentativo di violare i confini di un ambiente di memoria gestita sono gestite dal runtime di detto ambiente, in questo caso la JVM. JVM è il suo processo, che esegue l'IL dell'applicazione. Se un programma tenta di effettuare una chiamata che estende lo stack di chiamate oltre i limiti o alloca più memoria di quella che la JVM può riservare, il runtime stesso inietterà un'eccezione, causando lo svolgimento dello stack di chiamate. Indipendentemente dalla quantità di memoria di cui il programma ha attualmente bisogno o dalla profondità del suo stack di chiamate, JVM avrà allocato memoria sufficiente all'interno dei propri limiti di processo per creare detta eccezione e iniettarla nel codice.


"la JVM avrà allocato memoria sufficiente all'interno dei propri limiti di processo per creare detta eccezione" ma se il codice mantiene un riferimento a tale eccezione, quindi non può essere riutilizzato, come può crearne un altro? O stai suggerendo che ha un oggetto OOME Singleton speciale?
Raedwald,

Non lo sto suggerendo. Se il tuo programma intercetta e si aggrappa a tutte le eccezioni mai create, comprese quelle create e iniettate dalla JVM o dal sistema operativo, alla fine la stessa JVM supererà alcuni limiti impostati dal sistema operativo e il sistema operativo lo spegnerà per un GPF o errore simile. Tuttavia, questo è cattivo design in primo luogo; le eccezioni dovrebbero essere gestite e quindi uscire dal campo di applicazione o essere eliminate. E non dovresti MAI provare a catturare e continuare su SOE o OOME; diverso da "ripulire" in modo da poter uscire con garbo, non c'è niente che tu possa fare per continuare l'esecuzione in quelle situazioni.
KeithS,

"In primo luogo questo è cattivo design": design terribile. Ma pedanticamente, la JVM sarebbe conforme alle specifiche se fallisse in quel modo?
Raedwald,

4

Sembra che stia confondendo la memoria virtuale riservata dalla JVM in cui la JVM esegue i programmi Java con la memoria nativa del sistema operativo host in cui la JVM viene eseguita come processo nativo. La JVM sul tuo computer è in esecuzione nella memoria gestita dal sistema operativo, non nella memoria che la JVM ha riservato per eseguire i programmi Java.

Ulteriori letture:

E come nota finale, provare a catturare un java.lang.Error (e le sue classi discendenti) al fine di stampare una traccia stack potrebbe non fornire alcuna informazione utile. Vuoi invece una discarica dell'heap.


4

Per chiarire ulteriormente la risposta di @Graham Borland, funzionalmente, la JVM lo fa all'avvio:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Successivamente, la JVM esegue uno dei seguenti bytecode Java: 'new', 'anewarray' o 'multianewarray'. Questa istruzione fa sì che JVM esegua una serie di passaggi in una condizione di memoria insufficiente:

  1. Richiamare una funzione nativa, diciamo allocate(). allocate()tenta di allocare memoria per alcune nuove istanze di una particolare classe o matrice.
  2. Quella richiesta di allocazione fallisce, quindi la JVM invoca un'altra funzione nativa, per esempio doGC(), che tenta di eseguire la garbage collection.
  3. Quando quella funzione ritorna, allocate()tenta di allocare nuovamente la memoria per l'istanza.
  4. Se fallisce (*), allora la JVM, all'interno di allocate (), fa semplicemente un throw OOME;, facendo riferimento a OOME che ha istanziato all'avvio. Si noti che non è stato necessario allocare tale OOME, si riferisce solo ad esso.

Ovviamente, questi non sono passi letterali; varieranno da JVM a JVM nell'implementazione, ma questa è l'idea di alto livello.

(*) Una notevole quantità di lavoro avviene qui prima di fallire. La JVM tenterà di cancellare gli oggetti SoftReference, tenterà l'allocazione direttamente nella generazione posseduta quando si utilizza un collector generazionale e possibilmente altre cose, come la finalizzazione.


3

Le risposte che dicono che la JVM pre-assegnerà OutOfMemoryErrorssono effettivamente corrette.
Oltre a provare questo provocando una situazione di memoria insufficiente, possiamo semplicemente controllare l'heap di qualsiasi JVM (ho usato un piccolo programma che fa solo una sospensione, eseguendolo usando l'Hotspot JVM di Oracle dall'aggiornamento 31 di Java 8).

Usando jmapvediamo che sembrano esserci 9 istanze di OutOfMemoryError (anche se abbiamo molta memoria):

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

Possiamo quindi generare un dump dell'heap:

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

e aprilo usando Eclipse Memory Analyzer , dove una query OQL mostra che la JVM sembra effettivamente pre-allocare OutOfMemoryErrorsper tutti i possibili messaggi:

inserisci qui la descrizione dell'immagine

Il codice per la JVM di Java 8 Hotspot che in realtà li prealloca può essere trovato qui , e si presenta così (con alcune parti omesse):

...
// 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 questo codice mostra che JVM tenterà innanzitutto di utilizzare uno degli errori pre-allocati con spazio per una traccia dello stack, quindi ricaderà su uno senza una traccia dello stack:

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;
  }
}

Buon post, accetterò questo come risposta per una settimana per dargli maggiore visibilità prima di tornare alla risposta precedente.
Pacerier,

In Java 8 e versioni successive, lo spazio di generazione permanente è stato rimosso completamente e non è più possibile specificare la dimensione della memoria di allocazione dello spazio heap da Java 8 e versioni successive poiché hanno introdotto la gestione della memoria dei metadati di classe dinamica chiamata Metaspace. Sarebbe bello se potessi mostrare un pezzo di codice che si rivolge anche a PermGen e confrontarlo con Metaspace.
Buhake Sindi,

@BuhakeSindi - Non vedo che cosa ha a che fare la generazione permanente. I nuovi oggetti non vengono allocati nella generazione permanente come indicato nella risposta. Inoltre, non menzionate mai il fatto che OutOfMemoryErrors è pre-allocato (che è la risposta effettiva alla domanda).
Johan Kaving,

1
Ok, quello che sto dicendo è che da Java 8, l'allocazione degli oggetti viene calcolata in modo dinamico mentre prima era allocata su uno spazio heap predefinito, quindi forse è pre-allocata in anticipo. Anche se OOME è pre-allocato, viene eseguito un "calcolo" per determinare se è necessario lanciare OOME (quindi perché ho fatto riferimento alla specifica JLS).
Buhake Sindi,

1
Le dimensioni dell'heap Java sono predefinite in Java 8 come in passato. La generazione permanente faceva parte dell'heap che conteneva metadati di classe, stringhe internate e statica di classe. Aveva una dimensione limitata che doveva essere regolata separatamente dalla dimensione totale dell'heap. In Java 8 i metadati di classe sono stati spostati nella memoria nativa e le stringhe internate e le statistiche di classe sono state spostate nel normale heap Java (vedere ad esempio qui: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.