CodeGym /Corsi /JAVA 25 SELF /Architettura della memoria nella JVM: stack, heap, PermGe...

Architettura della memoria nella JVM: stack, heap, PermGen/MetaSpace

JAVA 25 SELF
Livello 64 , Lezione 0
Disponibile

1. Panoramica della memoria di un processo Java

Quando avvii un programma Java, la JVM (Java Virtual Machine) chiede al sistema operativo un po’ di memoria. A volte poca, a volte parecchia (specialmente se avvii, per esempio, Minecraft con un mucchio di mod). Questa memoria è divisa in alcune aree chiave, ciascuna con il proprio ruolo:

  • Stack (pila) — per variabili locali e chiamate ai metodi.
  • Heap — per tutti gli oggetti che crei con new.
  • Aree di servizio (PermGen/MetaSpace) — per metadati delle classi, campi statici e altre cose “magiche”.

A grandi linee appare così:

┌───────────────────────────────┐
│ Processo JVM                  │
│ ┌─────────────┐               │
│ │  Stack      │ ← Ogni thread ha il proprio stack!
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Condiviso tra tutti i thread
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ PermGen/      │ ← Metadati delle classi
│ │ MetaSpace     │
│ └───────────────┘             │
└───────────────────────────────┘

Perché è importante?

  • Capire come è organizzata la memoria aiuta a scrivere codice più efficiente e sicuro.
  • È più semplice diagnosticare errori come StackOverflowError o OutOfMemoryError.
  • Niente più paura di termini come “garbage collector” e “memory leak” — capisci dove e cosa cercare.

2. Stack: veloce, locale, ma non per sempre

Lo stack è un’area di memoria speciale, allocata separatamente per ogni thread. Lo stack assomiglia a una pila di piatti: l’ultimo appoggiato sarà il primo a essere rimosso, ma solo dopo aver tolto quelli sopra. In altre parole, lo stack funziona secondo il principio LIFO (Last In, First Out).

A cosa serve lo stack?

Nello stack si memorizzano:

  • Variabili locali dei metodi (per esempio, int x = 5; all’interno di un metodo).
  • L’indirizzo di ritorno dopo la chiamata di un metodo (per sapere dove tornare quando il metodo termina).

Ogni volta che chiami un metodo, nello stack viene aggiunto un nuovo frame (stack frame) — una sorta di “scatola” che contiene tutte le variabili locali di quel metodo e informazioni di servizio. Quando il metodo termina, il suo frame viene rimosso — tutte le sue variabili locali scompaiono.

Esempio

public static void main(String[] args) {
    int a = 10;           // a è nello stack di main
    int b = sum(a, 5);    // chiamiamo sum
}

public static int sum(int x, int y) {
    int result = x + y;   // x, y, result sono nello stack di sum
    return result;
}
  • Quando si chiama sum, viene creato per esso un frame separato nello stack.
  • Dopo il completamento di sum, le sue variabili scompaiono.

Ciclo di vita di una variabile

Le variabili locali vivono solo finché viene eseguito il metodo in cui sono dichiarate. Non appena il metodo termina, non esistono più e la memoria viene liberata immediatamente.

Stack overflow

Se per errore (o di proposito) scrivi una ricorsione infinita, ogni chiamata del metodo aggiungerà un nuovo frame nello stack. A un certo punto lo stack finirà e otterrai:

Exception in thread "main" java.lang.StackOverflowError

Esempio:

public static void main(String[] args) {
    recurse();
}
public static void recurse() {
    recurse(); // Ricorsione infinita!
}

Dimensione dello stack

La dimensione dello stack è limitata — di solito sono alcuni megabyte per thread (può essere impostata con il parametro -Xss). Se lo stack si esaurisce, il programma termina con un errore.

3. Heap: lo spazio per i tuoi oggetti

L’heap è un’area di memoria condivisa tra tutti i thread, dove vivono tutti gli oggetti creati con new, nonché gli array. È proprio nell’heap che avviene tutta la “magia” della programmazione orientata agli oggetti.

Come finiscono gli oggetti nell’heap?

String s = new String("Hello");
int[] arr = new int[10];
  • La variabile s è un riferimento e si trova nello stack.
  • L’oggetto String e l’array arr si trovano nell’heap.

Ciclo di vita di un oggetto

Un oggetto vive nell’heap finché esiste almeno un riferimento forte (strong reference) ad esso. Non appena nessuno fa più riferimento all’oggetto, esso diventa “spazzatura” e può essere rimosso dal garbage collector (GC).

Gestione della memoria

A differenza di C/C++, dove devi occuparti tu della liberazione della memoria (free, delete), in Java se ne occupa il GC. Non puoi liberare esplicitamente un oggetto, ma puoi azzerare tutti i riferimenti ad esso: in tal caso diventerà un candidato per la rimozione.

Schema: dove risiede cosa?

Stack (main)
  └─ s ─┬────────────┐
         │           │
         ▼           │
      Heap           │
   ┌─────────────┐   │
   │ String "Hello"◄──┘
   └─────────────┘

Caratteristiche dell’heap

  • L’heap è unico per l’intero processo JVM.
  • La dimensione dell’heap può essere impostata all’avvio (-Xmx, -Xms).
  • Se nell’heap non rimane spazio libero e il GC non riesce a liberare memoria, il programma termina con l’errore OutOfMemoryError.

4. PermGen e MetaSpace: dove vivono le classi?

Quando scrivi class MyClass { ... } e poi avvii il programma, la JVM deve memorizzare da qualche parte tutto ciò che riguarda quella classe — metodi, campi, bytecode, variabili statiche, costanti e persino i letterali di stringa. Per questo nella JVM esiste un’area di memoria speciale dove “vivono” le classi.

In passato, prima di Java 8, quest’area si chiamava PermGen (Permanent Generation). Ma presentava non pochi problemi — per esempio, aveva una dimensione fissa e, se lo spazio non bastava, l’applicazione semplicemente andava in crash con l’errore OutOfMemoryError: PermGen space.

Con Java 8 è arrivata una nuova area più flessibile — MetaSpace. Ha sostituito la vecchia PermGen e ora può espandersi automaticamente, occupando tutta la memoria necessaria al sistema (nei limiti della memoria fisica disponibile).

PermGen (fino a Java 8)

  • In PermGen erano memorizzati metadati delle classi, campi statici, letterali di stringa.
  • La dimensione di PermGen era limitata (di default ridotta) e poteva essere aumentata con il parametro -XX:MaxPermSize=256m.
  • Se un’applicazione caricava dinamicamente molte classi (per esempio nei server web), PermGen poteva “esaurirsi” e si otteneva l’errore:
java.lang.OutOfMemoryError: PermGen space
  • Problema: la pulizia di PermGen non avveniva sempre correttamente se le classi venivano scaricate dinamicamente (per esempio durante il reload delle applicazioni web).

MetaSpace (Java 8+)

  • Con Java 8 PermGen è scomparsa ed è comparsa MetaSpace.
  • MetaSpace memorizza i metadati delle classi, ma ora nella memoria nativa (al di fuori della Java Heap).
  • La dimensione di MetaSpace non è limitata di default (è limitata solo dalla memoria del sistema), ma è possibile impostare un limite con -XX:MaxMetaspaceSize=512m.
  • L’errore in caso di memoria insufficiente ora è il seguente:
java.lang.OutOfMemoryError: Metaspace
  • In MetaSpace finiscono anche campi statici, metodi e informazioni sulle classi.

Schema: come è organizzato il tutto

┌───────────────────────────────┐
│ Processo JVM                  │
│ ┌─────────────┐               │
│ │  Stack      │ ← Variabili locali, chiamate ai metodi
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Oggetti, array, tutto ciò che è creato con new
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ MetaSpace     │ ← Metadati delle classi, campi statici
│ └───────────────┘             │
└───────────────────────────────┘

Perché è importante?

Se scrivi normali applicazioni desktop o server, con ogni probabilità non ti imbatterai mai in errori di PermGen o MetaSpace. Ma se lavori con il caricamento dinamico delle classi (per esempio plugin, applicazioni web, framework come Spring che possono caricare e scaricare molte classi), allora conoscere MetaSpace è un must-have!

5. Illustrazione: schema della memoria della JVM

flowchart TD
    subgraph JVM
        direction TB
        Stack1["Stack (Thread 1)"]
        Stack2["Stack (Thread 2)"]
        Heap[Heap]
        MetaSpace[MetaSpace]
    end
    Stack1 --fa riferimento a--> Heap
    Stack2 --fa riferimento a--> Heap
    Heap --utilizza le classi da--> MetaSpace
  • Ogni thread ha il proprio stack.
  • Tutti gli stack possono fare riferimento agli oggetti nell’heap.
  • Gli oggetti nell’heap “conoscono” la propria classe, la cui informazione risiede in MetaSpace.

6. Esempio: come appare nel codice reale

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 42; // x è nello stack di main
        String s = "Hello!"; // s è un riferimento nello stack; l'oggetto String è nell'heap; il letterale "Hello!" è in MetaSpace
        Person p = new Person("Alice"); // p è un riferimento nello stack, l'oggetto Person è nell'heap

        // Chiamiamo un metodo per creare un nuovo frame dello stack
        printPerson(p);
    }

    public static void printPerson(Person person) {
        // person è un riferimento nello stack di printPerson
        System.out.println(person.getName());
    }
}

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() { return name; }
}

Analisi:

  • x è una variabile locale che vive nello stack del metodo main.
  • s è un riferimento nello stack, l’oggetto String è nell’heap e il letterale di stringa "Hello!" è in MetaSpace.
  • p è un riferimento nello stack, l’oggetto Person è nell’heap.
  • La classe Person e tutti i suoi metodi/campi sono in MetaSpace (metadati delle classi).
  • La chiamata printPerson(p) crea un nuovo frame dello stack; al suo interno il riferimento locale person punta allo stesso oggetto nell’heap.

7. Come la JVM gestisce la memoria: breve FAQ

Posso gestire lo stack?
No, lo stack è completamente sotto il controllo della JVM. Puoi solo impostarne la dimensione all’avvio (-Xss).

Posso gestire l’heap?
Parzialmente: la dimensione dell’heap si imposta all’avvio (-Xmx, -Xms). La pulizia è responsabilità del garbage collector (GC).

Posso gestire MetaSpace?
Puoi limitarne la dimensione (-XX:MaxMetaspaceSize), ma di solito non è necessario.

Cosa succede in caso di memoria insufficiente?
— Se si esaurisce lo stack: StackOverflowError.
— Se si esaurisce l’heap: OutOfMemoryError: Java heap space.
— Se si esaurisce MetaSpace: OutOfMemoryError: Metaspace.

8. Errori tipici nella gestione della memoria

Errore n. 1: StackOverflowError a causa di ricorsione infinita. La causa più comune è dimenticare di prevedere una condizione di uscita dalla ricorsione. Per esempio, un metodo che chiama sé stesso senza fermarsi. La JVM non può espandere lo stack all’infinito e il programma “crolla”.

Errore n. 2: OutOfMemoryError per esaurimento dell’heap. Se crei troppi oggetti a cui continuano a fare riferimento variabili/collezioni (per esempio aggiungi elementi a una lista senza mai rimuoverli), l’heap può esaurirsi.

Errore n. 3: OutOfMemoryError: PermGen space / Metaspace. Se usi plugin o carichi dinamicamente molte classi, e MetaSpace non viene ripulita (per esempio a causa di uno scaricamento scorretto delle classi), lo spazio in MetaSpace può esaurirsi.

Errore n. 4: Confusione tra riferimento e oggetto. Molti principianti confondono le cose: la variabile di tipo Person nello stack è solo un riferimento, mentre l’oggetto vero e proprio è nell’heap.

Errore n. 5: Aspettarsi che il garbage collector elimini tutto all’istante. Il GC lavora “a modo suo” (in realtà secondo algoritmi interni e in caso di carenza di memoria), non immediatamente dopo che hai azzerato un riferimento. Non fare affidamento su un rilascio istantaneo della memoria.

1
Compito
JAVA 25 SELF, livello 64, lezione 0
Bloccato
StackOverflowError – La vivace cascata di ricorsione 🧙‍♂️
StackOverflowError – La vivace cascata di ricorsione 🧙‍♂️
1
Compito
JAVA 25 SELF, livello 64, lezione 0
Bloccato
OutOfMemoryError – Il divoratore di memoria insaziabile 💾
OutOfMemoryError – Il divoratore di memoria insaziabile 💾
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION