Java Heap Space vs Stack Memory

Per eseguire le applicazioni in modo ottimizzato la JVM (java virtual machine) divide la memoria in due aree denominate stack ed heap memory. Nella letteratura specializzata sono molti i riferimenti a tali aree ma difficilmente troverete una spiegazione completa e comprensibile di come sono utilizzate.

Java Heap Space

Lo Heap Space è utilizzato dal runtime di java per allocare dinamicamente la memoria necessaria a contenere gli oggetti e le classi del JRE. Ogni volta che in un programma viene creato un qualsiasi oggetto, esso viene sempre memorizzato nello Heap Space, ed un riferimento allo stesso è mantenuto dello Stack Space.

La Garbage Collection viene eseguito nello Heap Space al fine di liberare la memoria utilizzata dagli oggetti che non hanno più alcun riferimento. Qualsiasi oggetto creato nello Heap Space è di tipo Global Access, ovvero può essere referenziato da qualsiasi parte nell’applicazione.

Tale area di memoria è ulteriormente suddiviso in sotto-aree più piccole chiamate Generation, e sono:

  • Young Generation: è qui dove tutti i nuovi oggetti vengono allocati. Una Garbage Collection minimale è eseguita su tale area quando questa si riempie.
  • Old o Tenured Generation: questa è l’area in cui sono conservati gli oggetti che da più tempo sono nello Heap Space. Quando un oggetto è allocato nella Young Generation, per esso viene impostata una soglia di età, al raggiungimento della quale l’oggetto viene spostato nella Old Generation.
  • Permanent Generation: contiene tutti i metadati  utilizzati dalla JVM per tenere traccia delle classi runtime e dei metodi applicativi.

Java Stack Memory

Lo Stack Memory in Java viene utilizzato per l’allocazione della memoria statica e per l’esecuzione dei thread. Contiene i valori primitivi specifici di un metodo ed i riferimenti ad oggetti che si trovano nello Heap Space e che sono riferiti al metodo. La dimensione della memoria dello stack è molto inferiore rispetto alla memoria Heap.

La strategia di gestione di tale memoria è di tipo LIFO (Last-In-First-Out). Ciò implica che ogni volta che un nuovo metodo viene invocato, il runtime genera un nuovo blocco in cima allo stack che contiene informazioni specifiche del metodo, come le variabili primitive e i riferimenti agli oggetti. Non appena l’esecuzione del metodo termina, il blocco diventa inutilizzato, il controllo torna al metodo chiamante, e lo spazio torna disponibile per l’esecuzione del metodo successivo.

Utilizzo dell’Heap Space e del Memory Space

Per comprendere l’utilizzo di queste due aree di memoria dal runtime di java consideriamo il semplice esempio mostrato di seguito e descriviamo l’effetto che la sequenza delle operazioni ha sulle due aree di memoria.

Non appena il programma è eseguito, vengono caricate tutte le classi del runtime nello spazio di heap. Quando il metodo main() viene invocato il runtime crea un’area di memoria nell Stack Memory che sarà utilizzata dal thread il cui viene eseguito il metodo. In particolare in tale area è contenuto:

  • il valore 0 assunto dalla variabile primitiva count;
  • due riferimenti, obj e test, a due aree di memoria dello Heap che contengono gli oggetti Object e Memory.

Quando è invocato il metodo someMethod() un nuovo blocco di memoria è generato nella parte superiore dello Stack Memory per essere usato dal metodo. Poiché il parametro al metodo è passato per valore, il runtime crea un nuovo riferimento all’area nello Heap contenente Object.

Infine è invocato il metodo toString() sull’oggetto Object, i cui dettagli implementativi sono tralasciati per semplicità. Conseguentemente il runtime genera un nuovo blocco nello Stack Memory e la stringa restituita dal metodo è inserita nello String Pool. Come suggerisce il nome, lo String Pool è un pool di stringhe memorizzate nello Heap Memory di java. Un riferimento a tale stringa è poi inserito nel blocco di memoria associato al metodo someMethod().

Al termine dell’esecuzione del programma tutti i blocchi generati nello Stack Memory sono liberati e lo spazio nello Heap Space è recuperato dalla Garbage Collection.

Conclusioni

Sulla base delle spiegazioni di cui sopra, possiamo facilmente rilevare le seguenti differenze tra memoria Heap e Stack.

  1. Lo Heap Space è utilizzata da tutta l’applicazione, mentre Stack Memory è utilizzata solo da un thread di esecuzione.
  2. Ogni volta che viene creato un oggetto, esso viene sempre memorizzato nello Heap Space e lo Stack Memory contiene solamente il riferimento ad esso. Conseguentemente lo Stack Memory può contenere solo variabili primitive locali e variabili di riferimento per oggetti nello Heap.
  3. Gli oggetti memorizzati nello Heap Space sono accessibili a livello globale mentre i blocchi nello Stack memory non sono accessibile dagli altri thread.
  4. La gestione della memoria nello Stack Memory viene eseguita in modo LIFO mentre è più complessa per Heap Space perché è utilizzata globalmente. tale memoria infatti è suddivisa in Young Generation, Old-Generation ecc.
  5. La memoria dello Stack è di breve durata mentre la memoria Heap dura dall’inizio fino alla fine dell’esecuzione dell’applicazione.
  6. E’ possibile utilizzare le opzioni -Xms e -Xmx per definire la dimensione di avvio e massima della memoria Heap, e l’opzione -Xss per definire la dimensione della memoria dello Stack.
  7. Quando la memoria dello Stack è piena, il runtime lancia l’eccezione java.lang.StackOverFlowError, mentre se la memoria Heap è piena genera l’eccezione  java.lang.OutOfMemoryError.
  8. La dimensione dello Stack Memory è molto inferiore rispetto allo Heap Memory. A causa della semplicità nell’allocazione della memoria (LIFO), la memoria dello stack è molto veloce rispetto alla memoria heap.

How useful was this post?

Click on a star to rate it!

Average rating / 5. Vote count:

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!