Reputation: 7284
I'm trying to use a ReferenceQueue to free resources used by the garbage collected objects. The problem is that my reference queue is always empty, even if there is proof that one of the referenced objects was garbage collected. Here's a very simple, self-contained JUnit test that illustrates what I am trying to do (track the removal of an object):
@Test
public void weakReferenceTest() {
ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
Object myObject1 = new Object();
Object myObject2 = new Object();
WeakReference<Object> ref1 = new WeakReference<Object>(myObject1, refQueue);
WeakReference<Object> ref2 = new WeakReference<Object>(myObject2, refQueue);
myObject1 = null;
// simulate the application running and calling GC at some point
System.gc();
myObject1 = ref1.get();
myObject2 = ref2.get();
if (myObject1 != null) {
System.out.println("Weak Reference to MyObject1 is still valid.");
fail();
} else {
System.out.println("Weak Reference to MyObject1 has disappeared.");
}
if (myObject2 != null) {
System.out.println("Weak Reference to MyObject2 is still valid.");
} else {
System.out.println("Weak Reference to MyObject2 has disappeared.");
fail();
}
Reference<? extends Object> removedRef = refQueue.poll();
boolean trackedRemoval = false;
while (removedRef != null) {
if (removedRef == ref1) {
System.out.println("Reference Queue reported deletion of MyObject1.");
trackedRemoval = true;
} else if (removedRef == ref2) {
System.out.println("Reference Queue reported deletion of MyObject2.");
fail();
}
removedRef = refQueue.poll();
}
if (trackedRemoval == false) {
fail();
}
}
For me, this always prints:
Weak Reference to MyObject1 has disappeared.
Weak Reference to MyObject2 is still valid.
... which is fine, but the test always fails due to trackedRemoval
being false
at the end - the ReferenceQueue
is always empty.
Am I using the ReferenceQueue
and/or the WeakReference
s wrong? I also tried PhantomReference
s instead, but it makes no difference.
Interestingly, if you convert the Unit Test to a regular public static void main(String[] args)
method, then it works like a charm!
Can anyone explain this specific behaviour? I've been searching for an answer to this for quite a while now.
Upvotes: 1
Views: 1093
Reputation: 7284
Okay, this might not be a definitive answer, but at least it gets the Unit test from the question to run properly. Instead of the regular java.lang.ref.ReferenceQueue
, I tried using the following class:
public class MyReferenceQueue<T> {
private Set<WeakReference<T>> WeakReferences = new HashSet<WeakReference<T>>();
public void enqueue(final WeakReference<T> reference) {
if (reference == null) {
throw new IllegalArgumentException("Cannot add reference NULL!");
}
this.WeakReferences.add(reference);
}
public WeakReference<T> poll() {
Iterator<WeakReference<T>> iterator = this.WeakReferences.iterator();
while (iterator.hasNext()) {
WeakReference<T> ref = iterator.next();
T object = ref.get();
if (object == null) {
iterator.remove();
return ref;
}
}
return null;
}
}
The downsides of this approach are:
enqueue
the WeakReference
s to trackWeakReference
s, not for PhantomReference
s (because their get
method always returns null
by definition)With this, the Unit Test succeeds as intended. Plus, it does not rely on the GC to do anything in particular (like adding a WeakReference
to some queue), except that it invalidates the WeakReference
(which it always does reliably). Perhaps it does something super ugly that I'm not aware of, but for now it gets the job done.
EDIT
Here is the updated JUnit-Test that makes use of my custom implementation.
public class WeakReferenceTest {
@Test
public void weakReferenceTest() {
MyReferenceQueue<Object> refQueue = new MyReferenceQueue<Object>();
Object myObject1 = new Object();
Object myObject2 = new Object();
// note that the second constructor argument (the reference queue) is gone...
WeakReference<Object> ref1 = new WeakReference<Object>(myObject1);
WeakReference<Object> ref2 = new WeakReference<Object>(myObject2);
// ... instead we enqueue the references manually now
refQueue.enqueue(ref1);
refQueue.enqueue(ref2);
// the rest of the test remains the same
myObject1 = null;
// simulate the application running and calling GC at some point
System.gc();
myObject1 = ref1.get();
myObject2 = ref2.get();
if (myObject1 != null) {
System.out.println("Weak Reference to MyObject1 is still valid.");
fail();
} else {
System.out.println("Weak Reference to MyObject1 has disappeared.");
}
if (myObject2 != null) {
System.out.println("Weak Reference to MyObject2 is still valid.");
} else {
System.out.println("Weak Reference to MyObject2 has disappeared.");
fail();
}
Reference<? extends Object> removedRef = refQueue.poll();
boolean trackedRemoval = false;
while (removedRef != null) {
if (removedRef == ref1) {
System.out.println("Reference Queue reported deletion of MyObject1.");
trackedRemoval = true;
} else if (removedRef == ref2) {
System.out.println("Reference Queue reported deletion of MyObject2.");
fail();
}
removedRef = refQueue.poll();
}
if (trackedRemoval == false) {
fail();
}
}
}
This test succeeds as intended.
Upvotes: 0
Reputation: 280141
It seems like a race condition to me. When the GC determines that the object referenced by myObject1
is GCable, it will GC it and clear it from the WeakReference
. It will then add that WeakReference
to a "pending reference list". There is a Reference handler thread which will remove from that list and add to the appropriate ReferenceQueue
. The above is an implementation detail which supports the javadoc
At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.
When your code reaches
Reference<? extends Object> removedRef = refQueue.poll();
the reference handler thread must not have added your WeakReference
to the ReferenceQueue
and therefore the poll
returns null
.
Upvotes: 2