Tipi di riferimento Java: Strong, Weak, Soft, Phantom
Come i tipi di riferimento Java interagiscono con il garbage collector per cache, listener e pulizia delle risorse.
Il garbage collection sembra automatico, ma Java ti offre uno strumento per influenzarlo. Una variabile ordinaria è un riferimento forte (strong) che mantiene un oggetto in memoria. Il package java.lang.ref aggiunge tre gradi più deboli — soft, weak e phantom — che consentono al garbage collector di recuperare un oggetto anche mentre si detiene ancora un riferimento ad esso. Questi tipi di riferimento sono alla base di cache memory-sensitive, registri di listener privi di memory leak e pulizia affidabile delle risorse.
La raggiungibilità decide chi sopravvive
Il garbage collector mantiene un oggetto in vita finché è raggiungibile da una radice GC (un thread attivo, un campo statico, una variabile nello stack). La forza dei riferimenti sul percorso verso un oggetto ne determina la classe di raggiungibilità, e quella classe ne decide il destino quando la memoria è necessaria.
| Riferimento | get() restituisce l'oggetto? | Il GC lo mantiene in vita? | Uso tipico |
|---|---|---|---|
| Strong | Sempre | Sempre (mentre è raggiungibile in modo strong) | Variabili e campi ordinari |
| Soft | Finché la memoria non scarseggia | Fino a quando l'heap non è sotto pressione | Cache memory-sensitive |
| Weak | Fino al prossimo ciclo GC che lo elimina | No | Mappe canonicalizzanti, listener |
| Phantom | Mai (sempre null) | No | Pulizia post-mortem |
L'ordine dal più forte al più debole è: strong → soft → weak → phantom. Quando più riferimenti di diversa forza puntano a un stesso oggetto, vince il più forte — un singolo riferimento strong è sufficiente a mantenere un oggetto vivo per sempre.
Riferimenti strong: il valore predefinito
Ogni riferimento scritto senza l'API java.lang.ref è strong. Finché un riferimento strong è raggiungibile, l'oggetto non può essere raccolto — questa è la fonte della maggior parte dei memory leak, in cui una voce dimenticata in una collezione longeva mantiene gli oggetti in vita indefinitamente.
List<byte[]> cache = new ArrayList<>();
cache.add(new byte[10_000_000]); // 10 MB pinned by a strong reference
// Nothing here can be collected until 'cache' itself becomes unreachable.Riferimenti soft: cache memory-sensitive
Un SoftReference consente al collector di recuperare l'oggetto solo quando l'heap sta esaurendo la memoria. Quando la memoria è abbondante, get() continua a restituire l'oggetto, il che rende i riferimenti soft la soluzione naturale per cache che devono ridursi sotto pressione anziché causare un OutOfMemoryError.
SoftReference<BufferedImage> ref = new SoftReference<>(loadThumbnail(path));
BufferedImage cached = ref.get();
if (cached == null) { // GC reclaimed it under memory pressure
cached = loadThumbnail(path); // recompute and re-wrap
ref = new SoftReference<>(cached);
}
return cached;La JVM garantisce che tutti i riferimenti soft agli oggetti softly-reachable vengano eliminati prima di lanciare OutOfMemoryError, quindi una cache basata su soft reference è l'ultima cosa a essere sacrificata, non la prima.
Riferimenti weak: mappe e listener
Un WeakReference non ritarda affatto la raccolta. Non appena un oggetto è solo weakly reachable, diventa candidato per il GC e get() restituirà null dopo il successivo ciclo di raccolta. Questo è esattamente quello che si vuole per le chiavi in una cache che dovrebbe scomparire quando nessun altro le usa — ed è su questo che è costruito WeakHashMap.
WeakHashMap<Widget, Metadata> sidecar = new WeakHashMap<>();
sidecar.put(widget, metadata);
// When 'widget' is no longer strongly referenced elsewhere, the entry
// vanishes automatically — no manual remove(), no leak.Abbinare un riferimento a una ReferenceQueue consente di essere notificati quando il referente viene raccolto: il GC mette in coda il riferimento eliminato in modo da poter eseguire la logica di follow-up.
ReferenceQueue<Widget> queue = new ReferenceQueue<>();
WeakReference<Widget> ref = new WeakReference<>(widget, queue);
// later, on a cleanup thread:
Reference<?> dead = queue.remove(); // blocks until a referent is collectedRiferimenti phantom: pulizia post-mortem
Un PhantomReference è il grado più debole e il più specializzato. Il suo get() restituisce sempre null, quindi non è mai possibile resuscitare l'oggetto attraverso di esso. Il suo unico scopo è essere messo in coda dopo che l'oggetto è stato raccolto, fornendo un hook sicuro per rilasciare risorse native — il sostituto moderno e affidabile del deprecato finalize().
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(resource, queue);
// A background thread drains the queue and frees the off-heap buffer
// only once the JVM confirms the object is truly gone.Il java.lang.ref.Cleaner del JDK (Java 9+) è costruito su riferimenti phantom ed è ciò a cui dovresti ricorrere nel codice reale invece di gestire la coda manualmente.
Un esempio completo: tutti e quattro i gradi in un'unica esecuzione
Questo programma crea un oggetto tenuto solo da ciascun tipo di riferimento, forza una garbage collection con System.gc() e riporta cosa è sopravvissuto. Collega anche delle ReferenceQueue ai riferimenti weak e phantom in modo da poter osservare le notifiche del GC per ogni "morte".
Cosa ricavare dall'esecuzione:
- Il riferimento strong ha stampato lo stesso
Resource(kept-alive)all'inizio e alla fine. Nonostante due chiamate aSystem.gc()nel mezzo, un oggetto strongly reachable non è mai candidato alla raccolta — i riferimenti strong vincono sempre. weak.get()ha restituitoResource(weakly-held)prima del GC manulldopo. Una volta che l'unico collegamento strong (onlyWeaklyHeld) è stato impostato anull, l'oggetto era solo weakly reachable, quindi il successivo ciclo di raccolta ha eliminato il riferimento weak.weak ref enqueued? : trueconferma che il GC ha inserito ilWeakReferenceeliminato nella suaReferenceQueue. Quell'accodamento è il meccanismo di notifica — è così cheWeakHashMape i registri di listener vengono informati che una voce può essere eliminata.soft.get()ha comunque restituitoResource(cache-entry)dopogc(). L'heap non era sotto pressione, quindi il collector ha mantenuto in vita l'oggetto softly-reachable — esattamente il comportamento che rende i riferimenti soft adatti alle cache che si riducono solo quando la memoria è scarsa.phantom.get()ha stampatonullanche prima di qualsiasi raccolta, eppurephantom enqueued? : truemostra che è stato comunque messo in coda una volta che il suo referente è morto. Un riferimento phantom non restituisce mai l'oggetto; esiste puramente per segnalare dopo il fatto che la pulizia può essere eseguita in sicurezza.
Argomenti correlati
I gradi di forza dei riferimenti hanno senso solo insieme a come la JVM recupera la memoria:
- Java Garbage Collection — cosa significa "raggiungibile" e quando il collector viene effettivamente eseguito.
- Java Memory Model — come oggetti, thread e visibilità si combinano.
- Java Stack vs Heap — dove vivono effettivamente gli oggetti a cui puntano i tuoi riferimenti.
- Java HashMap — la controparte con chiavi strong rispetto alla
WeakHashMapusata sopra.