I decided to continue https://stackoverflow.com/a/41998907/2674303 in a separated topic.
Let's consider following example:
public class SimpleGCExample {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue=new ReferenceQueue<>();
SimpleGCExample e = new SimpleGCExample();
Reference<Object> pRef=new PhantomReference<>(e, queue),
wRef=new WeakReference<>(e, queue);
e = null;
for(int count=0, collected=0; collected<2; ) {
Reference ref=queue.remove(100);
if(ref==null) {
System.gc();
count++;
}
else {
collected++;
System.out.println((ref==wRef? "weak": "phantom")
+" reference enqueued after "+count+" gc polls");
}
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing the object in "+Thread.currentThread());
Thread.sleep(100);
System.out.println("done finalizing.");
}
}
Java 11 prints following:
finalizing the object in Thread[Finalizer,8,system]
weak reference enqueued after 1 gc polls
done finalizing.
phantom reference enqueued after 3 gc polls
First 2 rows can change order. Looks like they work in parallel.
Last row sometimes prints 2 gc polls and sometimes 3
So I see that enqueing of PhantomReference takes more GC cycles. How to explain it? Is it mentioned somewhere in documentation(I can't find)?
P.S.
WeakReference java doc:
Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues
PhantomReference java doc:
Suppose the garbage collector determines at a certain point in time that an object is phantom reachable. At that time it will atomically clear all phantom references to that object and all phantom references to any other phantom-reachable objects from which that object is reachable. At the same time or at some later time it will enqueue those newly-cleared phantom references that are registered with reference queues
Difference is not clear for me
I got answer to my question from @Holger:
He(no sexism but I suppose so) pointed me to the java doc and noticed that PhantomReference contains extra phrase in comparison with Soft and Weak References:
An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference. When the weak references to a weakly-reachable object are cleared, the object becomes eligible for finalization.
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
My next question was about what does it mean it has been finalized I expected that it means that finalize method was finished
To prove it I modified application like this:
public class SimpleGCExample {
static SimpleGCExample object;
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SimpleGCExample e = new SimpleGCExample();
Reference<Object> pRef = new PhantomReference<>(e, queue),
wRef = new WeakReference<>(e, queue);
e = null;
for (int count = 0, collected = 0; collected < 2; ) {
Reference ref = queue.remove(100);
if (ref == null) {
System.gc();
count++;
} else {
collected++;
System.out.println((ref == wRef ? "weak" : "phantom")
+ " reference enqueued after " + count + " gc polls");
}
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing the object in " + Thread.currentThread());
Thread.sleep(10000);
System.out.println("done finalizing.");
object = this;
}
}
I see following output:
weak reference enqueued after 1 gc polls
finalizing the object in Thread[Finalizer,8,system]
done finalizing.
And application hangs. I think it is because for Weak/Soft references GC works in a following way: As soon as GC detected that object is Weak/Soft Reachable it does 2 actions in parallel:
So for adding into ReferenceQueue it doesn't matter if object was resurrected or not.
But for PhantomReference actions are different. As soon as GC detected that object is Phantom Reachable it does following actions sequentially:
But @Holger said that it has been finalized means that JVM initiated finalize() method invocation and for adding PhantomReference into ReferenceQueue it doesn't matter if it finished or not. But looks like my example shows that it really matter.
Frankly speaking I don't understand the difference according to adding into RefernceQueue for Weak and Soft Reference. What was the idea?
The key point is the definition of “phantom reachable” in the package documentation:
- 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.
bold emphasis mine
Note that when we remove the finalize()
method, the phantom reference gets collected immediately, together with the weak reference.
This is the consequence of JLS §12.6:
For efficiency, an implementation may keep track of classes that do not override the finalize method of class Object, or override it in a trivial way.
…
We encourage implementations to treat such objects as having a finalizer that is not overridden, and to finalize them more efficiently, as described in §12.6.1.
Unfortunately, §12.6.1 does not go into the consequences of “having a finalizer that is not overridden”, but it’s easy to see, that the implementation just treats those objects like being already finalized, never enqueuing them for finalization and hence, being able to reclaim them immediately, which affects the majority of all objects in typical Java applications.
Another point of view is that the necessary steps for ensuring that the finalize()
method will eventually get invoked, i.e. the creation and linking of a Finalizer
instance, will be omitted for objects with a trivial finalizer. Also, eliminating the creation of purely local objects after Escape Analysis, only works for those objects.
Since there is no behavioral difference between weak references and phantom references for objects without a finalizer, we can say that the presence of finalization, and its possibility to resurrect objects, is the only reason for the existence of phantom references, to be able to perform an object’s cleanup only when it is safe to assume that it can’t get resurrected anymore¹.
¹ Though, before Java 9, this safety was not bullet-proof, as phantom references were not automatically cleared and deep reflection allowed to pervert the whole concept.