W3docs

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
StrongSiempreSiempre (mientras sea alcanzable con fuerza)Variables y campos ordinarios
SoftHasta que la memoria escaseaHasta que el heap está bajo presiónCachés sensibles a la memoria
WeakHasta que el siguiente GC la eliminaNoMapas de canonicalización, listeners
PhantomNunca (siempre null)NoLimpieza 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 collected

Referencias 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.

java— editable, runs on the server

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 a System.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 pero null después. Una vez que el único enlace strong (onlyWeaklyHeld) se estableció en null, el objeto solo era weakly reachable, por lo que la siguiente recolección eliminó la referencia weak.
  • weak ref enqueued? : true confirma que el GC colocó el WeakReference eliminado en su ReferenceQueue. Ese encolamiento es el mecanismo de notificación — así es como WeakHashMap y los registros de listeners saben que una entrada puede ser eliminada.
  • soft.get() aún devolvió Resource(cache-entry) después de gc(). 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ó null incluso antes de cualquier recolección, pero phantom enqueued? : true muestra 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 WeakHashMap usado arriba.

Práctica

Práctica
Necesitas una caché que almacene valores calculados para acelerar tu programa, pero que libere memoria automáticamente en lugar de causar un OutOfMemoryError cuando el heap esté bajo. ¿Qué tipo de referencia es más adecuado?
Necesitas una caché que almacene valores calculados para acelerar tu programa, pero que libere memoria automáticamente en lugar de causar un OutOfMemoryError cuando el heap esté bajo. ¿Qué tipo de referencia es más adecuado?
Was this page helpful?