JavaRush /Blog Java /Random-ES /Referencia fantasma en Java

Referencia fantasma en Java

Publicado en el grupo Random-ES
¡Hola! En la lección de hoy, hablaremos en detalle sobre Phantom References en Java. ¿Qué tipo de enlaces son estos, por qué se llaman “fantasmas” y cómo utilizarlos? Como recordarás, existen 4 tipos de enlaces en Java:
  1. StrongReference (referencias regulares que creamos al crear un objeto):

    Cat cat = new Cat()

    cat en este ejemplo es una referencia fuerte.

  2. SoftReference (referencia suave). Tuvimos una conferencia sobre estos enlaces.

  3. WeakReference (referencia débil). También hubo una conferencia sobre ellos aquí .

  4. PhantomReference (referencia fantasma).

Se escriben objetos de los últimos tres tipos (por ejemplo, SoftReference<Integer> , WeakReference<MyClass> ). Clases SoftReferencey WeakReferenceheredar PhantomReferencede la clase Reference. Los métodos más importantes al trabajar con estas clases son:
  • get()- devuelve el objeto al que hace referencia este enlace;

  • clear()— elimina una referencia a un objeto.

Recuerdas estos métodos de conferencias sobre SoftReferencey WeakReference. Es importante recordar que funcionan de manera diferente con diferentes tipos de enlaces. Hoy no consideraremos en detalle los tres primeros tipos, pero hablaremos de enlaces fantasma. También tocaremos otros tipos de enlaces, pero sólo en la parte donde hablaremos de en qué se diferencian los enlaces fantasma de ellos. ¡Ir! :) Comencemos con por qué necesitamos enlaces fantasma en primer lugar. Como sabes, el recolector de basura (Garbage Collector o gc) se encarga de liberar memoria de objetos Java innecesarios . El recolector retira el objeto en dos "pasadas". En la primera pasada, solo mira los objetos y, si es necesario, los marca como "innecesarios y para eliminar". Si este objeto tenía un método anulado finalize(), se llama. O no se llama, dependiendo de tu suerte. Probablemente recuerdes que esto finalize()es algo voluble :) En la segunda pasada del recopilador, el objeto se elimina y se libera la memoria. Este comportamiento impredecible del recolector de basura nos crea una serie de problemas. No sabemos exactamente cuándo comenzará a funcionar el recolector de basura. No sabemos si se llamará al método finalize(). Además, durante la operación finalize()se puede crear un vínculo fuerte con el objeto y luego no se eliminará en absoluto. En sistemas que consumen mucha memoria, esto puede llevar fácilmente a OutOfMemoryError. Todo esto nos empuja a utilizar enlaces fantasma . El caso es que esto cambia el comportamiento del recolector de basura. Si solo quedan enlaces fantasmas al objeto, entonces tiene:
  • se llama al método finalize()(si se anula);

  • Si después del trabajo finalize()nada ha cambiado y el objeto aún se puede eliminar, se coloca una referencia fantasma al objeto en una cola especial: ReferenceQueue.

Lo más importante que hay que entender cuando se trabaja con referencias fantasma es que un objeto no se elimina de la memoria mientras su referencia fantasma esté en esta cola . Se eliminará sólo después del enlace fantasma clear(). Veamos un ejemplo. Primero, creemos una clase de prueba que almacenará algunos datos.
public class TestClass {
   private StringBuffer data;
   public TestClass() {
       this.data = new StringBuffer();
       for (long i = 0; i < 50000000; i++) {
           this.data.append('x');
       }
   }
   @Override
   protected void finalize() {
       System.out.println("У un objetoа TestClass вызван метод finalize!!!");
   }
}
"Cargamos" objetos deliberadamente con datos al crearlos (agregando 50 millones de caracteres "x" a cada objeto) para ocupar más memoria. Además, anulamos específicamente el método finalize()para ver que funcionó. A continuación necesitamos una clase que herede de PhantomReference. ¿Por qué necesitamos una clase así? Es sencillo. De esta manera podemos agregar lógica adicional al método clear()para ver que la referencia fantasma realmente se ha borrado (y por lo tanto el objeto se ha eliminado).
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {

   public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {

       super(obj, queue);

       Thread thread = new QueueReadingThread<TestClass>(queue);

       thread.start();
   }

   public void cleanup() {
       System.out.println("Очистка фантомной ссылки! Удаление un objetoа из памяти!");
       clear();
   }
}
A continuación, necesitaremos un hilo separado que esperará a que el recolector de basura haga su trabajo y ReferenceQueueaparecerán enlaces fantasmas en nuestra cola. Tan pronto como dicho enlace entre en la cola, se llamará a su método cleanup():
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class QueueReadingThread<TestClass> extends Thread {

   private ReferenceQueue<TestClass> referenceQueue;

   public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
       this.referenceQueue = referenceQueue;
   }

   @Override
   public void run() {

       System.out.println("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //Cómo только в очереди появилась фантомная enlace - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
Y finalmente, necesitamos un método main(): pongámoslo en una clase separada Main. En él crearemos un objeto TestClass, una referencia fantasma y una cola para referencias fantasma. Después de eso llamaremos al recolector de basura y veremos qué pasa :)
import java.lang.ref.*;

public class Main {

   public static void main(String[] args) throws InterruptedException {
       Thread.sleep(10000);

       ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
       Reference ref = new MyPhantomReference<>(new TestClass(), queue);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Salida de la consola: ref = MyPhantomReference@4554617c ¡El hilo que monitorea la cola ha comenzado! ¡Se llama recolección de basura! ¡¡¡Se ha llamado al método de finalización en el objeto TestClass !!! ref = MyPhantomReference@4554617c ¡Se está llamando a la recolección de basura! ¡Limpiando el enlace fantasma! ¡Eliminar un objeto de la memoria! ¿Qué vemos aquí? ¡Todo sucedió como lo planeamos! Nuestra clase de objeto tenía un método anulado finalize()y se llamó mientras se ejecutaba el recopilador. A continuación, el enlace fantasma se puso en cola ReferenceQueue. Allí tenía un método llamado clear()(desde el cual agregamos cleanup()salida a la consola). Como resultado, el objeto fue eliminado de la memoria. Ahora ves exactamente cómo funciona :) Por supuesto, no necesitas memorizar toda la teoría asociada con los enlaces fantasma. Pero será bueno que recuerdes al menos los puntos principales. En primer lugar , estos son los eslabones más débiles de todos. Sólo entran en juego cuando no hay otras referencias al objeto. La lista de enlaces que hemos proporcionado anteriormente está en orden descendente de fuerza: StrongReference-> SoftReference-> WeakReference-> PhantomReference El enlace fantasma entrará en batalla solo cuando no haya enlaces Fuertes, Suaves o Débiles a nuestro objeto :) En segundo lugar , el método get()para el enlace fantasma siempre regresa null. Aquí hay un ejemplo simple en el que creamos tres tipos diferentes de enlaces para tres tipos diferentes de automóviles:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       HybridAuto hybrid = new HybridAuto();
       F1Car f1car = new F1Car();

       SoftReference<Sedan> softReference = new SoftReference<>(sedan);
       System.out.println(softReference.get());

       WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
       System.out.println(weakReference.get());

       ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();

       PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
       System.out.println(phantomReference.get());

   }
}
Salida de la consola: Sedan@4554617c HybridAuto@74a14482 null El método get()devolvió objetos bastante normales para el enlace suave y el enlace débil, pero devolvió nullpara el fantasma. En tercer lugar , el principal ámbito de uso de las referencias fantasma son los procedimientos complejos para eliminar objetos de la memoria. ¡Eso es todo! :) Esto concluye nuestra lección de hoy. Pero la teoría por sí sola no te llevará muy lejos, ¡así que es hora de volver a resolver problemas! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION