Martin Häusler
Martin Häusler

Reputation: 7284

Why is ReferenceQueue always empty?

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 WeakReferences wrong? I also tried PhantomReferences 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

Answers (2)

Martin H&#228;usler
Martin H&#228;usler

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:

  • Need to explicitly enqueue the WeakReferences to track
  • Only works for WeakReferences, not for PhantomReferences (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

Sotirios Delimanolis
Sotirios Delimanolis

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

Related Questions