Search code examples
javaunit-testingweak-references

Testing WeakReference


What is the proper approach to testing a weak reference in Java?

My initial idea is to do the following:

public class WeakReferenceTest {

    public class Target{
        private String value;    

        public Target(String value){
            this.value = value;
        }    
        public String toString(){
            return value;
        }
    }

    public class UsesWeakReference{    
        WeakReference<Target> reference;   

        public UsesWeakReference(Target test){
            reference = new WeakReference<Target>(test);
        }    
        public String call(){
            Target test = reference.get();
            if(test != null){
                return test.toString();
            }
            return "empty";
        }
    }

    @Test
    public void testWeakReference(){    
        Target target = new Target("42");

        UsesWeakReference usesWeakReference = new UsesWeakReference(target);    
        WeakReference<Target> triggerReference = new WeakReference<Target>(target);    
        assertEquals("42", usesWeakReference.call());

        target = null;    
        while(triggerReference.get() != null){
            System.gc();
        }

        assertEquals("empty", usesWeakReference.call());    
    }    
}

The reservation I have about the approach is using System.gc(), as I understand that it can behave differently on different JVMs.


Solution

  • There's no 100% bombproof way of testing code that uses the Reference types. The behaviour of Reference objects depends on when the GC runs, and there is no 100% reliable way of forcing the GC to run.

    The best you can do is:

    • check that you have the right JVM options set when running the tests, and
    • write your test so that it doesn't fail in the event that System.gc() is a no-op OR be willing to disable or skip the test, or ignore the test failure.

    (You should be able to detect that System.gc() is being ignored by looking at how much memory is in use before and after the call; e.g. by calling Runtime.totalMemory())


    Actually, there is another "solution". Have your unit test generate a huge amount of garbage ... enough to guarantee that you will trigger garbage collection. (Not a good idea, IMO.)