La JVM (Java Virtual Machine) gestisce l’esecuzione dei programmi Java attraverso un modello di memoria virtuale strutturato in aree distinte, ognuna con un ruolo specifico.


HEAP

Caratteristiche:

  • Area di memoria più grande, condivisa tra tutti i thread.
  • Gestita dal Garbage Collector.
  • Divisa in Young Generation (Eden + Survivor spaces) e Old Generation.
  • Contiene il pool di stringhe.

Contenuto:


STACK

Caratteristiche:

  • Ogni thread ha il proprio stack.
  • Memoria veloce ma limitata.
  • I frame vengono creati quando un metodo viene chiamato e rimossi alla sua uscita.

Contenuto:


METASPACE

Caratteristiche:

  • Ha sostituito il vecchio PermGen (presente da Java 8 in poi).
  • Risiede nella memoria nativa.
  • Dimensione dinamica: cresce a seconda delle necessità, riducendo gli errori OutOfMemoryError.

Contenuto:

  • Metadati delle classi caricate (struttura della classe).
  • Constant pool.

PROGRAM COUNTER

Caratteristiche:

  • Ogni thread ha il proprio registro PC.
  • Contiene l’indirizzo della prossima istruzione da eseguire.

Contenuto:

  • Indice/posizione dell’istruzione corrente della JVM per quel thread.

NATIVE METHOD STACK

Caratteristiche:

  • Separato dallo stack Java.
  • Usato per l’esecuzione di metodi scritti in linguaggi nativi (C, C++).
  • Ogni thread ha il proprio Native Method Stack.

Contenuto:

  • Variabili locali e strutture di supporto per i metodi nativi invocati tramite JNI (Java Native Interface).

SCHEMA

+---------------------------+
|           Heap            |          
|   Location: JVM Memory    |
+---------------------------+
|        Metaspace          |
|   Location: Native Memory |
+---------------------------+
|           Stack           |
|   Location: Thread Memory |
+---------------------------+
|   Native Method Stack     |
|   Location: Thread Memory |
+---------------------------+
|       PC Register         |
|   Location: Thread Memory |
+---------------------------+
|        Code Cache         |
|   Location: Native Memory |
+---------------------------+
 

ESEMPIO DI GESTIONE

class Persona {
	
	static int contatore = 0;  
	// Memorizzato nell'HEAP (static -> metadato nel metaspace)
	
    String nome;  
    // Memorizzato nell'HEAP (parte dell'oggetto)
    
    public Persona(String nome) {
        this.nome = nome;  
        // La variabile nome è salvata nell'HEAP
        contatore++;  
        // La variabile statica rimane nell'HEAP
    }
    
    public void mostraNome() {
        System.out.println("Nome: " + nome); 
        // La variabile "nome" è recuperata dall'HEAP
    }
    
    // Eseguito nello stack del thread (static -> bytecode nel metaspace)
    public static void mostraContatore() {
        System.out.println("Numero di persone create: " + contatore);
    }
}
 
public class StackHeapExample {
    public static void main(String[] args) {
    
        int x = 10;  
        // Memorizzato nello STACK (variabile locale)  
        
        Persona p1 = new Persona("Travis");  
        // "p1" è nello STACK, ma il suo oggetto (Travis) è nell'HEAP
        
        Persona p2 = new Persona("Kanye");    
        // "p2" è nello STACK, ma il suo oggetto (Kanye) è nell'HEAP
        
        p1.mostraNome();  
        // Recupera "Travis" dall'HEAP
        
        p2.mostraNome();  
        // Recupera "Kanye" dall'HEAP
		
        Persona.mostraContatore();
		/* Chiamata a un metodo statico:
			non dipende da un'istanza,
			bytecode nel Metaspace,
			esecuzione nello stack del thread */
    }
}

In generale, la memoria in Java è progettata per gestire oggetti, variabili locali e metadati in modo separato e ottimizzato, con l’aiuto del Garbage Collector per evitare perdite di memoria.