Types de références Java : Strong, Weak, Soft, Phantom
Comment les niveaux de référence Java interagissent avec le garbage collector pour les caches, listeners et nettoyages.
Le ramasse-miettes semble automatique, mais Java vous offre un curseur pour l'influencer. Une variable ordinaire est une référence forte qui épingle un objet en mémoire. Le package java.lang.ref ajoute trois niveaux plus faibles — soft, weak et phantom — qui permettent au garbage collector de récupérer un objet même si vous en détenez encore un handle. Ces types de références constituent le fondement des caches sensibles à la mémoire, des registres de listeners sans fuite, et du nettoyage fiable des ressources.
L'accessibilité détermine qui survit
Le garbage collector maintient un objet en vie tant qu'il est accessible depuis une racine GC (un thread actif, un champ statique, une variable de pile). La force des références sur le chemin vers un objet détermine sa classe d'accessibilité, et cette classe décide de son sort lorsque de la mémoire est nécessaire.
| Référence | get() retourne l'objet ? | Le GC le garde en vie ? | Utilisation typique |
|---|---|---|---|
| Strong | Toujours | Toujours (tant qu'il est fortement accessible) | Variables et champs ordinaires |
| Soft | Jusqu'à ce que la mémoire soit faible | Jusqu'à ce que le tas soit sous pression | Caches sensibles à la mémoire |
| Weak | Jusqu'au prochain cycle GC | Non | Maps canoniques, listeners |
| Phantom | Jamais (toujours null) | Non | Nettoyage post-mortem |
L'ordre du plus fort au plus faible est : strong → soft → weak → phantom. Lorsque plusieurs références de différentes forces pointent vers un même objet, la plus forte l'emporte — une seule référence forte suffit à maintenir un objet en vie indéfiniment.
Références fortes : la valeur par défaut
Chaque référence que vous écrivez sans l'API java.lang.ref est forte. Tant qu'une référence forte est accessible, l'objet ne peut pas être collecté — c'est la source de la plupart des fuites mémoire, où une entrée oubliée dans une collection de longue durée maintient des objets en vie indéfiniment.
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.Références soft : caches sensibles à la mémoire
Une SoftReference permet au collecteur de récupérer l'objet uniquement lorsque le tas est presque plein. Tant que la mémoire est abondante, get() continue de retourner l'objet, ce qui fait des références soft le choix naturel pour les caches qui doivent se réduire sous pression plutôt que provoquer une 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 garantit que toutes les références soft vers des objets soft-accessibles sont effacées avant de lever une OutOfMemoryError, de sorte qu'un cache à références soft est le dernier sacrifié, pas le premier.
Références weak : maps et listeners
Une WeakReference ne retarde pas du tout la collecte. Dès qu'un objet n'est que faiblement accessible, il devient éligible au GC et get() retournera null après le prochain cycle de collecte. C'est exactement ce que vous souhaitez pour les clés d'un cache qui doit disparaître lorsque personne d'autre ne les utilise — c'est ce sur quoi WeakHashMap est construit.
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.Associer une référence à une ReferenceQueue vous permet d'être notifié lorsque le référent est collecté : le GC met en file d'attente la référence effacée afin que vous puissiez exécuter une logique de suivi.
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 collectedRéférences phantom : nettoyage post-mortem
Une PhantomReference est le niveau le plus faible et le plus spécialisé. Sa méthode get() retourne toujours null, vous ne pouvez donc jamais ressusciter l'objet par son intermédiaire. Son seul but est d'être mise en file d'attente après que l'objet a été collecté, vous offrant un point d'ancrage sûr pour libérer des ressources natives — le remplacement moderne et fiable du finalize() déprécié.
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.Le propre java.lang.ref.Cleaner du JDK (Java 9+) est construit sur les références phantom et c'est ce que vous devriez utiliser dans du vrai code plutôt que de gérer la file d'attente manuellement.
Un exemple concret : les quatre niveaux en une seule exécution
Ce programme crée un objet détenu uniquement par chaque type de référence, force un ramasse-miettes avec System.gc(), et indique ce qui a survécu. Il connecte également des ReferenceQueues aux références weak et phantom afin que vous puissiez observer les notifications du GC à chaque suppression.
Ce que l'exécution nous enseigne :
- La référence forte a affiché le même
Resource(kept-alive)au début et à la fin. Malgré deux appelsSystem.gc()entre les deux, un objet fortement accessible n'est jamais candidat à la collecte — les références fortes l'emportent toujours. weak.get()a retournéResource(weakly-held)avant le GC maisnullaprès. Une fois le seul lien fort (onlyWeaklyHeld) mis ànull, l'objet n'était plus qu'accessiblefaiblement, et le prochain cycle de collecte a donc effacé la référence weak.weak ref enqueued? : trueconfirme que le GC a poussé laWeakReferenceeffacée dans saReferenceQueue. Cet enqueue est le mécanisme de notification — c'est ainsi queWeakHashMapet les registres de listeners apprennent qu'une entrée peut être purgée.soft.get()a encore retournéResource(cache-entry)aprèsgc(). Le tas n'était sous aucune pression, donc le collecteur a maintenu l'objet soft-accessible en vie — exactement le comportement qui rend les références soft adaptées aux caches qui ne se réduisent que lorsque la mémoire est faible.phantom.get()a affichénullmême avant toute collecte, maisphantom enqueued? : truemontre qu'il a quand même été mis en file d'attente une fois son référent mort. Une référence phantom ne rend jamais l'objet ; elle existe uniquement pour signaler après coup que le nettoyage peut s'exécuter en toute sécurité.
Sujets connexes
Les niveaux de référence n'ont de sens qu'au regard de la façon dont la JVM récupère la mémoire :
- Java Garbage Collection — ce que signifie « accessible » et quand le collecteur s'exécute réellement.
- Java Memory Model — comment les objets, les threads et la visibilité s'articulent ensemble.
- Java Stack vs Heap — où vivent réellement les objets vers lesquels pointent vos références.
- Java HashMap — le pendant à clés fortes de la
WeakHashMaputilisée ci-dessus.