java garbage-collection phantom-reference

Java: PhantomReference, ReferenceQueue y finalize



garbage-collection phantom-reference (4)

La Referencia Phantom no aparecerá en la ReferenceQueue hasta que el objeto haya sido finalizado. Estás haciendo un ciclo ocupado por lo que es problemático. Tenga en cuenta que la finalización requiere al menos dos gcs.

Tengo un PR, un objeto O al que apunta PR, y un RQ configurado para PR. Tengo un hilo que sigue sondeando el RQ, y en la primera referencia que encuentra en RQ, el hilo imprime la hora en que lo encontró y sale.

Las cosas funcionan bien, pero en el momento en que O tiene una finalización (no importa cuán trivial), el hilo ya no encuentra una referencia en RQ y sigue ejecutándose indefinidamente.

Pregunta: ¿por qué sucede de esta manera? Estoy usando Sun JDK 1.6.

Aquí está el código:

good case

public class MyGCPhantom { public static void main(String[] args) throws InterruptedException { GCPhantomObject p = new GCPhantomObject(); ReferenceQueue phantomQueue = new ReferenceQueue(); PhantomReference<GCPhantomObject> pr = new PhantomReference<GCPhantomObject>(p, phantomQueue); new GCPhantomThread(phantomQueue, "Phantom").start(); p = null; System.gc(); } } class GCPhantomObject { @Override protected void finalize() { //System.out.println("GCPhantom finalized " + System.currentTimeMillis()); } } class GCPhantomThread extends Thread { private ReferenceQueue referenceQueue; private String name; GCPhantomThread(ReferenceQueue referenceQueue, String name) { this.referenceQueue = referenceQueue; this.name = name; } @Override public void run() { while(referenceQueue.poll() == null); System.out.println(name + " found at " + System.currentTimeMillis()); } }

bad case

Simplemente elimine el comentario del SOP en finalize() de GCPhantomObject .


Acabo de probar el código publicado aquí en mi sistema y no funciona incluso después de dos llamadas a System.gc (). No termina incluso si coloca System.gc () llama en el ciclo while de la clase GCPhantomThread aquí.

Me parece que el problema aquí es que el objeto que está creando nunca se coloca en la ReferenceQueue porque ni siquiera es accesible a fantasmas cuando se ejecuta GCPhantomThread. La referencia fantasma al objeto en el método main () queda fuera del alcance y, por lo tanto, cuando se ejecuta el GCPhantomThread, el objeto ni siquiera es accesible fantasma. De acuerdo con los documentos, para que la referencia fantasma sea enrutada, es necesaria la finalización y la posibilidad de acceso fantasma.

Funciona cuando paso la referencia ficticia a GCPhantomThread. En mi máquina este código siempre termina:

import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.List; import java.util.Random; public class MyGCPhantom { public static void main(String[] args) throws InterruptedException { GCPhantomObject p = new GCPhantomObject(); ReferenceQueue phantomQueue = new ReferenceQueue(); PhantomReference pr = new PhantomReference(p, phantomQueue); new GCPhantomThread(pr, phantomQueue, "Phantom").start(); p = null; pr = null; System.gc(); System.out.println("main thread done ..."); } } class GCPhantomObject { @Override protected void finalize() { System.out.println("GCPhantom finalized at " + System.nanoTime()); } } class GCPhantomThread extends Thread { private ReferenceQueue referenceQueue; private String name; private PhantomReference pr; GCPhantomThread(PhantomReference pr, ReferenceQueue referenceQueue, String name) { this.referenceQueue = referenceQueue; this.name = name; this.pr = pr; } @Override public void run() { try { while (referenceQueue.remove(5000) == null) { System.gc(); } System.out.println(name + " found at " + System.nanoTime()); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }


Tu análisis está algo apagado. Tanto en el caso bueno como en el malo, su objeto implementa finalize . En el buen caso, lo implementa trivialmente; en el caso malo, no trivial. Por lo tanto, el problema obvio es la diferencia entre una implementación trivial y no trivial de finalize .

No veo ninguna razón por la cual JVM sea forzado por especificación para poner en cola a sus refs. Realiza una única ejecución de GC y luego sigue esperando que ocurra algo. Se sabe que cualquier finalizador no trivial puede resucitar el objeto, por lo tanto, puede haber más ciclos de GC necesarios antes de ponerlo en cola. Sugiero agregar más llamadas a GC.

También tenga en cuenta que su decisión de utilizar poll lugar de remove no es aconsejable. Debería usar una llamada de bloqueo para evitar la votación ocupada.

Como referencia, estas son las definiciones relevantes de la documentación:

Si el recolector de basura determina en un cierto punto en el tiempo que el referente de una referencia fantasma es accesible a nivel fantasma, entonces en ese momento o en algún momento posterior enrutará la referencia.

Un objeto es alcanzable fantasma si no es fuerte, débil o débilmente alcanzable, se ha finalizado y se hace referencia a algo fantasma.

Un objeto finalizado ha tenido su finalizador invocado automáticamente.


Encontré un excelente artículo sobre Finalizer de Jack Shirazi. La pregunta se responde una vez que revisamos el artículo.

http://www.fasterj.com/articles/finalizer1.shtml

Para explicarlo en pocas palabras: en el caso de un método finalize () no trivial, aunque el objeto se ''recolecta'' en la primera ejecución de GC, no se elimina físicamente en ese momento. Eso sucede en la próxima carrera de GC. Es por eso que el objeto PR aparecerá en la cola durante el segundo GC.