The statement “The Garbage Collector adds a phantom reference to a reference queue after the finalize method of its referent is executed.” is a bit sloppy at best.
You should refer to the specification:
If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.
Whereas the linked definition of “phantom reachable” states:
An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.
So an object is phantom reachable “after the finalize method of its referent is executed” if only referenced by phantom references and hence will be enqueued after that but not immediately. Since an object is strongly reachable during the execution of its finalize()
method, it takes at least one additional garbage collection cycle to detect that it became phantom reachable. Then, “at that time or at some later time” it will get enqueued.
If you change the program to
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Object object = new Object() {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize()");
}
};
PhantomReference<Object> phantomReference=new PhantomReference<>(object, referenceQueue);
object = null;
System.gc();
Thread.sleep(1_000);
System.gc();
Thread.sleep(1_000);
System.out.println("isEnqueued() after GC: " + phantomReference.isEnqueued());
Reference reference = referenceQueue.poll();
if(reference != null) {
System.out.println("isEnqueued() after poll(): " + phantomReference.isEnqueued());
}
You will most likely see the desired output, but it has to be emphasized that there are no guarantees that the garbage collector will actually run when you call System.gc()
or that it completes in a particular amount of time when it runs or that it will find all unreachable objects within a particular cycle. Further, the enqueuing happens asynchronously after the gc cycle so even by the time the garbage collector completed and detected a special reachability state, an additional amount of time may pass before the reference gets enqueued.
Note that the sentence “It implies that the instance is still in the memory.” also doesn’t get it right, but in this case, it’s based on a misunderstanding that was even on the Java core developer’s side.
When the API was created, a sentence was added to the specification which you will find even in the Java 8 version:
Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.
This may lead to the naïve assumption that the object still has to be in memory, but The Java® Language Specification states:
Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable.
Simply said, the memory of objects may get reclaimed earlier, if the behavior of the program does not change. This applies especially to scenarios, where the application can not use the object at all, like with the phantom reference. The behavior of the program wouldn’t change if the object is not in memory anymore, so you can not assume that it actually is.
This leads to the question, why the rule that phantom references are not cleared was added to the spec at all. As discussed in this answer, that question was brought up and could not be answered at all. Consequently, this rule has been removed in Java 9 and phantom reference are cleared when enqueued, like weak and soft reference. That’s an even stronger reason not to assume that the object is still in memory, as now even non-optimizing environments can reclaim the object’s memory at this point.