Los tipos de referencias débiles soft, weak y phantom en Java

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

Java

Cuando un objeto ya no es alcanzable a través de ninguna referencia directa o cadena de referencias fuertes el objeto es seleccionable para reclamar su memoria y el recolector de basura o garbage collector de Java lo hace cuando estima oportuno, liberándonos a los programadores de esta tarea, simplificando el código y evitando fugas de memoria. El lenguaje Java le debe al recolector de basura entre otras varias cosas una buena parte de su éxito.

En Java en realidad hay 4 tipos de referencias a objetos, además de las fuertes hay otras 3 más débiles que no impiden al recolector de basura reclamar el objeto referenciado. Es raro tener la necesidad de usar otra que no sean las fuertes o strong pero es interesante conocerlas por si en algún caso nos resultase de utilidad. Los otros 3 tipos de referencias denominadas débiles son SoftReference, WeakReference y PhantomReference que extienden de Reference. Usar una de estas otras 3 referencias es muy simple basta usar el constructor de cada tipo de referencia.

Después de la llamada de varias veces al recolector de basura en este caso de forma explícita con el método System.gc() las referencias son encoladas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.github.picodotdev.blogbitix.javareference;

import java.lang.ref.*;
import java.util.UUID;

public class Main {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<String> queue = new ReferenceQueue<>();

	String strong = new String("Hello World!");
        SoftReference<String> soft = new SoftReference<>(new String("Hello World!"), queue);
        WeakReference<String> weak = new WeakReference<>(new String("Hello World!"), queue);
        PhantomReference<String> phantom = new PhantomReference<>(new String("Hello World!"), queue);

	System.gc();
	System.gc();
	System.gc();
	System.gc();
	System.gc();

        Reference<? extends String> reference = queue.poll();
        while (reference != null) {
            System.out.println(reference.getClass().getName());
            reference = queue.poll();
        }
    }
}
Main.java
1
2
java.lang.ref.PhantomReference
java.lang.ref.WeakReference
System.out

El objeto de una referencia soft es recolectable a discreción del recolector de basura ante necesidades de memoria, el objeto de una referencias weak es recolectable si solo es alcanzable por referencias weak y las referencias phantom son una mejor y más flexible alternativa al mecanismo de finalización de los objetos.

Algunos usos prácticos de las referencias soft y weak son como caches de datos posiblemente usando la clase WeakHashMap, en el caso de las referencias phantom como mecanismo alternativo a la finalización de objetos incorporada en los objetos desde la versión inicial de Java.

El mecanismo de finalización de los objetos Java con el método finalize que puede ser implementado por cualquier clase presenta los siguientes problemas:

  • La llamada al método finalize es impredecible ya que depende de cuando del recolector de basura reclame el objeto.
  • No hay garantía de que el método finalize sea llamado ya que puede perdurar durante toda la vida de la JVM.
  • Una referencia fuerte al objeto puede ser revivida en el método finalize si se implementa de forma inadecuada.

En los constructores de las referencias débiles se puede indicar un ReferenceQueue en el que se encolará la referencia cuando el objeto al que referencia cambia su alcanzabilidad. Este mecanismo de notificación es utilizado con las referencias phantom para proporcionar el mecanismo de finalización alternativo. En la documentación javadoc con la descripción del paquete de las referencias se comenta este proceso de notificación. Las referencias son encoladas cuando el recolector de basura determina que son solo alcanzables por referencias soft, weak o phantom.

En el artículo Replacing Finalizers With Phantom References se explica junto con su código como implementar el mecanismo alternativo al método finalize. La librería Guava proporciona las clases FinalizablePhantomReference y FinalizableReferenceQueue con una forma un poco más sencilla de usar las referencias phantom, en esa documentación también hay un ejemplo de código con su uso para liberar un recurso (ServerSocket) asociado a un objeto (MyServer).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 public class MyServer implements Closeable {
   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
   // You might also share this between several objects.

   private static final Set<Reference<?>> references = Sets.newConcurrentHashSet();
   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.

   private final ServerSocket serverSocket;

   private MyServer(...) {
     ...
     this.serverSocket = new ServerSocket(...);
     ...
   }

   public static MyServer create(...) {
     MyServer myServer = new MyServer(...);
     final ServerSocket serverSocket = myServer.serverSocket;
     Reference<?> reference = new FinalizablePhantomReference<MyServer>(myServer, frq) {
       public void finalizeReferent() {
         references.remove(this):
         if (!serverSocket.isClosed()) {
           ...log a message about how nobody called close()...
           try {
             serverSocket.close();
           } catch (IOException e) {
             ...
           }
         }
       }
     };
     references.add(reference);
     return myServer;
   }

   public void close() {
     serverSocket.close();
   }
 }
MyServer.java

Las referencias débiles añaden una indirección a la referencia que contienen, usando el método get() se accede al objeto referenciado pero hay que tener en en cuenta que el método get puede devolver un null ya que no impiden al recolector de basura reclamar el objeto referenciado, en el caso de las PhantomReferences el método get siempre devuelve null para evitar que la referencia a un objeto sea revivida.

Otro artículo que recomiendo leer es Weak, Soft, and Phantom References in Java (and Why They Matter), explica el concepto de estas referencias con un símil más fácil de comprender de un restaurante y sus clientes que dependiendo de su comportamiento se asemeja a estas referencias y el por que de los recolectores de basura, que no es algo novedoso de Java sino que ya fué utilizado en 1959 con el lenguaje Lisp.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: