Tipos de referencia en Java: Strong, Weak, Soft, Phantom
Cómo las referencias en Java interactúan con el recolector de basura para habilitar cachés, listeners y limpieza de recursos.
La recolección de basura parece automática, pero Java te da un dial para influir en ella. Una variable ordinaria es una referencia fuerte (strong reference) que fija un objeto en memoria. El paquete java.lang.ref añade tres grados más débiles — soft, weak y phantom — que permiten al recolector de basura recuperar un objeto incluso mientras sigues teniendo un handle a él. Estos tipos de referencia son la base de cachés sensibles a la memoria, registros de listeners sin fugas y limpieza confiable de recursos.
La alcanzabilidad decide quién vive
El recolector de basura mantiene un objeto vivo mientras sea alcanzable desde una raíz de GC (un hilo activo, un campo estático, una variable en la pila). La fortaleza de las referencias en el camino hacia un objeto determina su clase de alcanzabilidad, y esa clase decide su destino cuando se necesita memoria.
| Referencia | ¿get() devuelve el objeto? | ¿El GC la mantiene viva? | Uso típico |
|---|---|---|---|
| Strong | Siempre | Siempre (mientras sea alcanzable con fuerza) | Variables y campos ordinarios |
| Soft | Hasta que la memoria escasea | Hasta que el heap está bajo presión | Cachés sensibles a la memoria |
| Weak | Hasta que el siguiente GC la elimina | No | Mapas de canonicalización, listeners |
| Phantom | Nunca (siempre null) | No | Limpieza post-mortem |
El orden de más fuerte a más débil es: strong → soft → weak → phantom. Cuando varias referencias de diferentes fortalezas apuntan a un objeto, la más fuerte gana — una sola referencia strong es suficiente para mantener un objeto vivo para siempre.
Referencias strong: el valor predeterminado
Toda referencia que escribes sin la API java.lang.ref es strong. Mientras una referencia strong sea alcanzable, el objeto no puede ser recolectado — esta es la fuente de la mayoría de las fugas de memoria, donde una entrada olvidada en una colección de larga duración mantiene objetos vivos indefinidamente.
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.Referencias soft: cachés sensibles a la memoria
Una SoftReference permite al recolector recuperar el objeto solo cuando el heap está bajo . Mientras la memoria sea abundante, get() sigue devolviendo el objeto, lo que hace que las referencias soft sean la opción natural para cachés que deben reducirse bajo presión en lugar de causar 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 garantiza que todas las referencias soft a objetos softly-reachable se eliminan antes de lanzar OutOfMemoryError, por lo que una caché de referencias soft es lo último que se sacrifica, no lo primero.
Referencias weak: mapas y listeners
Una WeakReference no retrasa la recolección en absoluto. En el instante en que un objeto es solo weakly reachable, se vuelve elegible para el GC y get() devolverá null después del siguiente ciclo de recolección. Esto es exactamente lo que quieres para las claves en una caché que debería desaparecer cuando nadie más las usa — que es en lo que está construido 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.Combinar una referencia con una ReferenceQueue te permite ser notificado cuando el referente es recolectado: el GC encola la referencia eliminada para que puedas ejecutar lógica de seguimiento.
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 collectedReferencias phantom: limpieza post-mortem
Una PhantomReference es el grado más débil y el más especializado. Su get() siempre devuelve null, por lo que nunca puedes resucitar el objeto a través de ella. Su único propósito es ser encolada después de que el objeto ha sido recolectado, dándote un hook seguro para liberar recursos nativos — el reemplazo moderno y confiable del finalize() obsoleto.
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.El propio java.lang.ref.Cleaner del JDK (Java 9+) está construido sobre referencias phantom y es a lo que deberías recurrir en código real en lugar de gestionar la cola manualmente.
Un ejemplo práctico: las cuatro fortalezas en una sola ejecución
Este programa crea un objeto mantenido solo por cada tipo de referencia, fuerza una recolección de basura con System.gc(), y reporta qué sobrevivió. También conecta ReferenceQueues a las referencias weak y phantom para que puedas ver cómo el GC te notifica de cada muerte.
Lo que hay que destacar de la ejecución:
- La referencia strong imprimió el mismo
Resource(kept-alive)al inicio y al final. A pesar de dos llamadas aSystem.gc()en medio, un objeto strongly reachable nunca es candidato para la recolección — las referencias strong siempre ganan. weak.get()devolvióResource(weakly-held)antes del GC peronulldespués. Una vez que el único enlace strong (onlyWeaklyHeld) se estableció ennull, el objeto solo era weakly reachable, por lo que la siguiente recolección eliminó la referencia weak.weak ref enqueued? : trueconfirma que el GC colocó elWeakReferenceeliminado en suReferenceQueue. Ese encolamiento es el mecanismo de notificación — así es comoWeakHashMapy los registros de listeners saben que una entrada puede ser eliminada.soft.get()aún devolvióResource(cache-entry)después degc(). El heap no estaba bajo presión, por lo que el recolector mantuvo el objeto softly-reachable vivo — exactamente el comportamiento que hace que las referencias soft sean adecuadas para cachés que solo se reducen cuando la memoria es escasa.phantom.get()imprimiónullincluso antes de cualquier recolección, perophantom enqueued? : truemuestra que aún fue encolado una vez que su referente murió. Una referencia phantom nunca devuelve el objeto; existe puramente para señalar después del hecho que la limpieza puede ejecutarse de forma segura.
Temas relacionados
Las fortalezas de referencia solo tienen sentido junto con cómo la JVM recupera memoria:
- Java Garbage Collection — qué significa "alcanzable" y cuándo el recolector realmente se ejecuta.
- Java Memory Model — cómo los objetos, los hilos y la visibilidad encajan.
- Java Stack vs Heap — dónde viven realmente los objetos a los que apuntan tus referencias.
- Java HashMap — la contraparte con claves strong del
WeakHashMapusado arriba.