5

Here is my code

public class FinalizableObject {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize() invoked for " + this);
        super.finalize();
    }
}

public class Main {
    private static void test() throws InterruptedException {
        ReferenceQueue<FinalizableObject> rq = new ReferenceQueue<FinalizableObject>();
        FinalizableObject obj = new FinalizableObject();
        PhantomReference<FinalizableObject> pr1 = new PhantomReference<FinalizableObject>(obj, rq);
        obj = null;
        System.gc();
        Reference<? extends Object> ref = rq.remove();
        System.out.print("remove " + ref + " from reference queue\n");
    }
    public static void main(String[] args) {
        try {
            test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

It's very strange, rq.remove() will be blocked forever. Why my finalizable object's phantom reference can not be put into reference queue? Has it been GC collected?

Devboard Fan
  • 199
  • 1
  • 10

1 Answers1

1

The problem is with your non-trivial finalize() method. In default implementation (in class Object) this method is actually empty. When its implementation isn't empty, then it is not guaranteed that object will be instantly collected after invoking finalize().

If you will modify your program in such style:

    for (int i = 0; i  < 1000; i++)
        System.gc();

i.e. you will call GC more then one times - it could lead to the rq object be fully collected (test on my machine).

Also, I suggest you following links to explore:

  1. Java: PhantomReference, ReferenceQueue and finalize
  2. Discovering Objects with Non-trivial Finalizers
  3. The Secret Life Of The Finalizer: page 2 of 2

UPD: Another way: you have to hold in mind, that non-trivial finalize() methods is invoked by special Finalizer-Thread, which also have to be collected. So, for full pr collecting you can do such things:

a) create flag inside Main method:

public static volatile boolean flag;

b) set flag in finalize() method:

@Override
protected void finalize() throws Throwable {
    System.out.println("finalize() invoked for " + this);
    super.finalize();
    Main.flag = true;
}

c) check flag for true and then call gc() again:

    System.gc();
    while (!flag) Thread.sleep(10);
    System.gc();
Community
  • 1
  • 1
Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • Thank you very much.
    I know the FinalizerReferenceQueue and FinalizerDeamon, 2 times of GC can fully collect an finalizable object. But I can not understand why 100 times of GC will trigger it to be collected (even 20 times of GC can not trigger the enqueue).
    On the other hand, if I replace the PhantomReference by WeakReference, it can remove the reference very quickly.
    – Devboard Fan Jul 25 '13 at 07:45
  • @DevboardFan This is because for invoking non-trivial `finalize()` method JVM constructs special Finalizer-Thread, which also have to be collected and finished. – Andremoniy Jul 25 '13 at 07:52
  • @DevboardFan BTW, you can create `public static volatile boolean flag;` in `Main()` class, set this flag in `finalize()` method and then wait in `Main()` until this flag will be set (after 1st `.gc()`). After that you can call `System.gc()` one more time. This will be enough. – Andremoniy Jul 25 '13 at 07:58
  • I see. You mean the finalize() method will be invoked after 1 time of GC, but the object were still not reclaimed really (from heap). It is strange for me why it needs so many GC to trigger the real reclaim procedure (not call finalize()). Thank you again. – Devboard Fan Jul 25 '13 at 08:31