W3docs

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.

Riferimentoget() restituisce l'oggetto?Il GC lo mantiene in vita?Uso tipico
StrongSempreSempre (mentre è raggiungibile in modo strong)Variabili e campi ordinari
SoftFinché la memoria non scarseggiaFino a quando l'heap non è sotto pressioneCache memory-sensitive
WeakFino al prossimo ciclo GC che lo eliminaNoMappe canonicalizzanti, listener
PhantomMai (sempre null)NoPulizia 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 collected

Riferimenti 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".

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il riferimento strong ha stampato lo stesso Resource(kept-alive) all'inizio e alla fine. Nonostante due chiamate a System.gc() nel mezzo, un oggetto strongly reachable non è mai candidato alla raccolta — i riferimenti strong vincono sempre.
  • weak.get() ha restituito Resource(weakly-held) prima del GC ma null dopo. Una volta che l'unico collegamento strong (onlyWeaklyHeld) è stato impostato a null, l'oggetto era solo weakly reachable, quindi il successivo ciclo di raccolta ha eliminato il riferimento weak.
  • weak ref enqueued? : true conferma che il GC ha inserito il WeakReference eliminato nella sua ReferenceQueue. Quell'accodamento è il meccanismo di notifica — è così che WeakHashMap e i registri di listener vengono informati che una voce può essere eliminata.
  • soft.get() ha comunque restituito Resource(cache-entry) dopo gc(). 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 stampato null anche prima di qualsiasi raccolta, eppure phantom enqueued? : true mostra 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 WeakHashMap usata sopra.

Pratica

Pratica
Hai bisogno di una cache che contenga valori calcolati per velocizzare il tuo programma, ma che li rilasci automaticamente invece di causare un OutOfMemoryError quando l'heap si esaurisce. Quale tipo di riferimento è più adatto?
Hai bisogno di una cache che contenga valori calcolati per velocizzare il tuo programma, ma che li rilasci automaticamente invece di causare un OutOfMemoryError quando l'heap si esaurisce. Quale tipo di riferimento è più adatto?
Was this page helpful?