As I understand working use case of detecting when object was collected and memory already free for weak/soft references it polling this queue and when reference appear in queue we can be sure that memory free.
WeakReference ref = new WeakReference (new Object())
Why cannot I poll ref
and check that it became null ?
P.S.
according the link provided in comment:
If the garbage collector discovers an object that is weakly reachable,
the following occurs:
1.The WeakReference object's referent field is set
to null, thereby making it not refer to the heap object any longer.
2.The heap object that had been referenced by the WeakReference is
declared finalizable.
3.When the heap object's finalize() method is run
and its memory freed, the WeakReference object is added to its
ReferenceQueue, if it exists.
Thus if this article write truth and these steps ordered weakreference becomes null after step but object adds to the queue only on 3th step.
Is it truth?
Is it cause why?
Lets research code:
working canonical example:
public class TestPhantomRefQueue {
public static void main(String[] args)
throws InterruptedException {
Object obj = new Object();
final ReferenceQueue queue = new ReferenceQueue();
final WeakReference pRef =
new WeakReference(obj, queue);
obj = null;
new Thread(new Runnable() {
public void run() {
try {
System.out.println("Awaiting for GC");
// This will block till it is GCd
Reference prefFromQueue;
while (true) {
prefFromQueue = queue.remove();
if (prefFromQueue != null) {
break;
}
}
System.out.println("Referenced GC'd");
System.out.println(pRef.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// Wait for 2nd thread to start
Thread.sleep(2000);
System.out.println("Invoking GC");
System.gc();
}
}
this code output:
Awaiting for GC
Invoking GC
Referenced GC'd
null
Ok, I understand why it does work.
Lets change code a bit:
public class TestPhantomRefQueue {
public static void main(String[] args)
throws InterruptedException {
Object obj = new Object();
final ReferenceQueue queue = new ReferenceQueue();
final WeakReference pRef =
new WeakReference(obj, queue);
obj = null;
new Thread(new Runnable() {
public void run() {
try {
System.out.println("Awaiting for GC");
while (true) {
if (pRef.get() == null) {
Thread.sleep(100);
break;
}
}
System.out.println("Referenced GC'd");
System.out.println(pRef.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// Wait for 2nd thread to start
Thread.sleep(2000);
System.out.println("Invoking GC");
System.gc();
}
}
this variant hangs in while loop and output:
Awaiting for GC
Invoking GC
Please explain this behaviour.
With little modification your code would produce anticipated result. See code snippet below.
In your code, line
pRef.get() == null
would assign pRef.get()
to temporary slot in frame of method run()
. Event after condition is calculated, slot is not clear automatically.
Garbage collector treat all slots/local variable in active frames on stack as GC roots. Unintentionally you are creating strong reference to your object so it wont be cleared.
I modified version, I have moved pRef.get()
to nested method. Once execution returns from method its frame is disposed, so to reference to object remains to prevent GC to collect it.
Of cause, if JVM would recompile run()
method and inline isRefernceCollected(pRef)
call, it may break again.
In summary, reference queues give you deterministic and efficient way to handle reference. Pooling can work but it is fragile and depends of code compilation by javac and JVM JIT.
Modified code snippet.
public class TestPhantomRefQueue {
public static void main(String[] args)
throws InterruptedException {
Object obj = new Object();
final ReferenceQueue queue = new ReferenceQueue();
final WeakReference pRef =
new WeakReference(obj, queue);
obj = null;
new Thread(new Runnable() {
public void run() {
try {
System.out.println("Awaiting for GC");
while (true) {
if (isRefernceCollected(pRef)) {
Thread.sleep(100);
break;
}
}
System.out.println("Referenced GC'd");
System.out.println(pRef.get());
} catch (Exception e) {
e.printStackTrace();
}
}
protected boolean isRefernceCollected(final WeakReference pRef) {
return pRef.get() == null;
}
}).start();
// Wait for 2nd thread to start
Thread.sleep(2000);
System.out.println("Invoking GC");
System.gc();
}
}
Output
Awaiting for GC
Invoking GC
Referenced GC'd
null
As long as the original reference is available to any part of your program, it will not be garbage collected. Having access to the original reference in order to check it for being null would prevent the object from being ever freed. That's why you need special reference types to handle this situation.
It is not correct that a temporary reference will stay in a stack frame’s slot, like stated in this answer. However, when you poll a variable in a tight loop, an optimizer might create code keeping a reference for several iterations instead of reading the value from the main memory each time. Since System.gc()
is only a hint anyway and you are calling it exactly one time, there is no guaranty that this particular gc cycle will find this object to be unreachable, if the cycle ever happened. After that, since there are no subsequent GC cycles, your polling loop will run forever.
You have also misplaced your sleep
call. It will sleep after it detected the collection, which makes no sense. If you change
while (true) {
if (pRef.get() == null) {
Thread.sleep(100);
break;
}
}
to
while (true) {
if (pRef.get() == null) {
break;
}
Thread.sleep(100);
}
it will work in most environments. But you don’t need such a complicated example:
WeakReference<Object> ref=new WeakReference<>(new Object());
int count=0;
while(ref.get()!=null) {
System.gc();
count++;
}
System.out.println("collected after "+count+" polls");
works on most systems. On my system, it will collect the object right on the first cycle.
So what’s the main difference between polling a ReferenceQueue
and polling a Reference
?
As the name suggests, a ReferenceQueue
is a queue, allowing to handle more than one reference. Instead of polling thousands of references one after another to find out whether one of them has a collected referent, you can poll the ReferenceQueue
to get the first collected, if there is one. If none of them has been collected, you’re done right after the first poll.
So the polling costs of a reference queue scales with the number of collected objects, not with the total number of existing. A typical example is WeakHashMap
. After a key has been collected, the associated entry, which still has a reference to the value, must be removed from the backing array. Instead of iterating over the entire array, checking each entry for presence of the key, only the reference queue has to be polled for collected keys. Since the extended reference object has remembered the hash code, it can be found in the array in O(1)
time. This is crucial, as there is no dedicated cleanup method that anyone has to call. As it works like an ordinary map, just with weak key semantics, the polling happens transparently on every method call on that map. So it must be fast.