Stack di memoria e spazio heap in Java

1. Introduzione

Per eseguire un'applicazione in modo ottimale, JVM divide la memoria in stack e memoria heap. Ogni volta che dichiariamo nuove variabili e oggetti, chiamiamo un nuovo metodo, dichiariamo una stringa o eseguiamo operazioni simili, JVM designa la memoria per queste operazioni da Stack Memory o Heap Space.

In questo tutorial, discuteremo di questi modelli di memoria. Elencheremo alcune differenze chiave tra di loro, come sono archiviate nella RAM, le funzionalità che offrono e dove usarle.

2. Stack di memoria in Java

La memoria dello stack in Java viene utilizzata per l'allocazione della memoria statica e l'esecuzione di un thread. Contiene valori primitivi specifici di un metodo e riferimenti a oggetti che si trovano in un heap, a cui fa riferimento il metodo.

L'accesso a questa memoria è in ordine Last-In-First-Out (LIFO). Ogni volta che viene chiamato un nuovo metodo, viene creato un nuovo blocco in cima allo stack che contiene valori specifici per quel metodo, come variabili primitive e riferimenti a oggetti.

Quando il metodo termina l'esecuzione, lo stack frame corrispondente viene scaricato, il flusso torna al metodo chiamante e lo spazio diventa disponibile per il metodo successivo.

2.1. Caratteristiche principali della memoria dello stack

Oltre a quanto discusso finora, di seguito sono riportate alcune altre funzionalità della memoria dello stack:

  • Cresce e si restringe quando vengono rispettivamente chiamati e restituiti nuovi metodi
  • Le variabili all'interno dello stack esistono solo finché il metodo che le ha create è in esecuzione
  • Viene allocato e deallocato automaticamente quando il metodo termina l'esecuzione
  • Se questa memoria è piena, Java lancia java.lang.StackOverFlowError
  • L'accesso a questa memoria è veloce rispetto alla memoria heap
  • Questa memoria è sicura perché ogni thread opera nel proprio stack

3. Spazio heap in Java

Lo spazio heap in Java viene utilizzato per l'allocazione dinamica della memoria per oggetti Java e classi JRE al runtime . I nuovi oggetti vengono sempre creati nello spazio heap ei riferimenti a questi oggetti vengono archiviati nella memoria dello stack.

Questi oggetti hanno accesso globale ed è possibile accedervi da qualsiasi punto dell'applicazione.

Questo modello di memoria è ulteriormente suddiviso in parti più piccole chiamate generazioni, queste sono:

  1. Young Generation: è qui che tutti i nuovi oggetti vengono assegnati e invecchiati. Quando si riempie, si verifica una Garbage Collection minore
  2. Generazione vecchia o di ruolo: è qui che vengono conservati gli oggetti sopravvissuti a lungo. Quando gli oggetti vengono memorizzati nella Young Generation, viene impostata una soglia per l'età dell'oggetto e quando viene raggiunta tale soglia, l'oggetto viene spostato nella vecchia generazione
  3. Generazione permanente: consiste di metadati JVM per le classi di runtime e i metodi dell'applicazione

Queste diverse parti sono anche discusse in questo articolo - Differenza tra JVM, JRE e JDK.

Possiamo sempre manipolare la dimensione della memoria heap secondo le nostre esigenze. Per ulteriori informazioni, visitare questo articolo di Baeldung collegato.

3.1. Caratteristiche principali della memoria heap Java

Oltre a quanto discusso finora, di seguito sono riportate alcune altre caratteristiche dello spazio heap:

  • Vi si accede tramite complesse tecniche di gestione della memoria che includono Young Generation, Old or Tenured Generation e Permanent Generation
  • Se lo spazio dell'heap è pieno, Java lancia java.lang.OutOfMemoryError
  • L'accesso a questa memoria è relativamente più lento della memoria dello stack
  • Questa memoria, a differenza dello stack, non viene deallocata automaticamente. Ha bisogno di Garbage Collector per liberare oggetti inutilizzati in modo da mantenere l'efficienza dell'utilizzo della memoria
  • A differenza dello stack, un heap non è sicuro per i thread e deve essere protetto sincronizzando correttamente il codice

4. Esempio

Sulla base di quanto appreso finora, analizziamo un semplice codice Java e valutiamo come viene gestita la memoria qui:

class Person { int id; String name; public Person(int id, String name) { this.id = id; this.name = name; } } public class PersonBuilder { private static Person buildPerson(int id, String name) { return new Person(id, name); } public static void main(String[] args) { int id = 23; String name = "John"; Person person = null; person = buildPerson(id, name); } }

Analizziamo questo passo dopo passo:

  1. Entrando nel metodo main () , verrebbe creato uno spazio nella memoria dello stack per memorizzare primitive e riferimenti di questo metodo
    • Il valore primitivo dell'id intero verrà archiviato direttamente nella memoria dello stack
    • La variabile di riferimento person di tipo Person verrà creata anche nella memoria dello stack che punterà all'oggetto effettivo nell'heap
  2. La chiamata al costruttore parametrizzato Person (int, String) da main () allocherà ulteriore memoria in cima allo stack precedente. Questo memorizzerà:
    • Il riferimento a questo oggetto dell'oggetto chiamante nella memoria dello stack
    • Il valore primitivo id nella memoria dello stack
    • La variabile di riferimento del nome dell'argomento String che punterà alla stringa effettiva dal pool di stringhe nella memoria heap
  3. Il metodo principale chiama ulteriormente il metodo statico buildPerson () , per il quale avrà luogo un'ulteriore allocazione nella memoria dello stack sopra quella precedente. Ciò memorizzerà nuovamente le variabili nel modo descritto sopra.
  4. Tuttavia, per l'oggetto person di tipo Person appena creato , tutte le variabili di istanza verranno archiviate nella memoria heap.

Questa allocazione è spiegata in questo diagramma:

5. Riepilogo

Prima di concludere questo articolo, riassumiamo rapidamente le differenze tra Stack Memory e Heap Space:

Parametro Stack di memoria Spazio dell'heap
Applicazione Stack viene utilizzato in parti, una alla volta durante l'esecuzione di un thread L'intera applicazione utilizza lo spazio Heap durante il runtime
Taglia Lo stack ha limiti di dimensione a seconda del sistema operativo e di solito è più piccolo di Heap Non esiste alcun limite di dimensione su Heap
Conservazione Memorizza solo le variabili primitive e i riferimenti agli oggetti creati in Heap Space Tutti gli oggetti appena creati vengono memorizzati qui
Ordine Vi si accede utilizzando il sistema di allocazione della memoria Last-in First-out (LIFO) Questa memoria è accessibile tramite complesse tecniche di gestione della memoria che includono Young Generation, Old or Tenured Generation e Permanent Generation.
Vita La memoria dello stack esiste solo finché il metodo corrente è in esecuzione Lo spazio dell'heap esiste finché l'applicazione è in esecuzione
Efficienza Comparativamente molto più veloce da allocare rispetto all'heap Più lento da allocare rispetto allo stack
Allocazione / Deallocazione Questa memoria viene allocata e deallocata automaticamente quando un metodo viene chiamato e restituito rispettivamente Lo spazio dell'heap viene allocato quando vengono creati nuovi oggetti e deallocati da Gargabe Collector quando non sono più referenziati

6. Conclusione

Stack e heap sono due modi in cui Java alloca la memoria. In questo articolo abbiamo capito come funzionano e quando utilizzarli per sviluppare programmi Java migliori.

Per saperne di più sulla gestione della memoria in Java, dai un'occhiata a questo articolo qui. Abbiamo anche discusso del Garbage Collector di JVM che viene discusso brevemente in questo articolo.