Java 101: Trash talk, Part 2

The Reference Objects API allows programs to interact with the garbage collector

1 2 3 Page 2
Page 2 of 3

After the garbage collector clears the referent's soft reference, it appends SoftReference's strong reference to the reference queue that q references. The addition of SoftReference happens when the garbage collector calls Reference's enqueue() method (behind the scenes). Your program can either poll the reference queue, by calling ReferenceQueue's poll() method, or block, by calling ReferenceQueue's no-argument remove() method, until the reference arrives on the queue. At that point, your program can either stop polling or automatically unblock. Because the poll() and remove() methods return a SoftReference object reference (through the Reference return types), you discover which soft reference cleared; you can then modify your cache as appropriate.

To demonstrate reference queues and soft references, I have created a SoftReferenceDemo application, which chooses to call poll() instead of remove() to increase the likelihood of garbage collection. If the application was to call remove(), the garbage collector might not run -- because the application doesn't constantly create objects and nullify their references. Hence, the application would remain blocked. Examine SoftReferenceDemo's source code in Listing 1:

Listing 1. SoftReferenceDemo.java

// SoftReferenceDemo.java
import java.lang.ref.*;
class Employee
{
   private String name;
   Employee (String name)
   {
      this.name = name;
   }
   public String toString ()
   {
      return name;
   }
}
class SoftReferenceDemo
{
   public static void main (String [] args)
   {
      // Create two Employee objects that are strongly reachable from e1
      // and e2.
      Employee e1 = new Employee ("John Doe");
      Employee e2 = new Employee ("Jane Doe");
      // Create a ReferenceQueue object that is strongly reachable from q.
      ReferenceQueue q = new ReferenceQueue ();
      // Create a SoftReference array with room for two references to
      // SoftReference objects. The array is strongly reachable from sr.
      SoftReference [] sr = new SoftReference [2];
      // Assign a SoftReference object to each array element. That object
      // is strongly reachable from that element. Each SoftReference object
      // encapsulates an Employee object that is referenced by e1 or e2 (so
      // the Employee object is softly reachable from the SoftReference
      // object), and associates the ReferenceQueue object, referenced by
      // q, with the SoftReference object.
      sr [0] = new SoftReference (e1, q);
      sr [1] = new SoftReference (e2, q);
      // Remove the only strong references to the Employee objects.
      e1 = null;
      e2 = null;
      // Poll reference queue until SoftReference object arrives.
      Reference r;
      while ((r = q.poll ()) == null)
      {
         System.out.println ("Polling reference queue");
         // Suggest that the garbage collector should run.
         System.gc ();
      }
      // Identify the SoftReference object whose soft reference was
      // cleared, and print an appropriate message.
      if (r == sr [0])
          System.out.println ("John Doe Employee object's soft reference " +
                              "cleared");
      else
          System.out.println ("Jane Doe Employee object's soft reference " +
                              "cleared");
      // Attempt to retrieve a reference to the Employee object.
 
      Employee e = (Employee) r.get ();
      // e will always be null because soft references are cleared before
      // references to their containing SoftReference objects are queued
      // onto a reference queue.
      if (e != null)
          System.out.println (e.toString ());
   }
}

When run, SoftReferenceDemo might poll the reference queue for a short time or a long time. The following output shows one SoftReferenceDemo invocation:

Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Jane Doe Employee object's soft reference cleared

SoftReferenceDemo calls System.gc (); (in a loop) to encourage the garbage collector to run. Eventually, at least on my platform, the garbage collector runs, and at least one soft reference clears. After that soft reference clears, a reference to the soft reference's enclosing SoftReference object appends to the reference queue. By comparing the returned reference with each sr array element reference, the program code can determine which Employee referent had its soft reference cleared. We can now recreate the Employee object, if so desired.

Weak references

The weakly reachable state manifests itself in Java through the WeakReference class. When you initialize a WeakReference object, you store a referent's reference in that object. The object contains a weak reference to the referent, and the referent is weakly reachable if there are no other references, apart from weak references, to that referent. If heap memory is running low, the garbage collector locates weakly reachable objects and clears their weak references -- by calling WeakReference's inherited clear() method. Assuming no other references point to those referents, the referents enter either the resurrectable state or the unreachable state. Assuming the referents enter the resurrectable state, the garbage collector calls their finalize() methods. If those methods do not make the referents reachable, the referents become unreachable, and the garbage collector can reclaim their memory.

Note
The primary difference between soft references and weak references is that the garbage collector might clear a soft reference but always clears a weak reference.

To create a WeakReference object, pass a reference to a referent in one of two constructors. For example, the following code fragment creates a ReferenceQueue object and uses the WeakReference(Object referent, ReferenceQueue q) constructor to create a WeakReference object (that encapsulates a Vehicle referent) and associate WeakReference with the reference queue:

ReferenceQueue q = new ReferenceQueue ();
WeakReference wr = new WeakReference (new Vehicle (), q);

Use weak references to obtain notification when significant objects are no longer strongly reachable. For example, suppose you create an application that simulates a company. That application periodically creates Employee objects. For each object, the application also creates an employee-specific Benefits object. During the simulation, employees eventually retire and their Employee objects are made eligible for garbage collection. Because it would prove detrimental for a Benefits object to hang around after its associated Employee object is garbage collected, the program should notice when Employee is no longer strongly reachable.

To obtain that notification, your code first creates WeakReference and ReferenceQueue objects. The code then passes Employee and ReferenceQueue object references to the WeakReference constructor. Next, the program stores the WeakReference object and a newly created Benefits object in a hash table data structure. That data structure forms an association between the WeakReference object (known as the key, because it identifies the hash table entry) and the Benefits object (known as that entry's value). The program can enter a polling loop where it keeps checking the reference queue to find out when the garbage collector clears the weak reference to the Employee object. Once that happens, the garbage collector places a reference to the WeakReference object on the reference queue. The program can then use that reference to remove the WeakReference/Benefits entry from the hash table. To see how the program accomplishes that task, check out Listing 2; it creates a String object as the key and an Object object as the value:

Listing 2. WeakReferenceDemo.java

// WeakReferenceDemo.java
import java.lang.ref.*;
import java.util.*;
class WeakReferenceDemo
{
   public static void main (String [] args)
   {
      // Create a String object that is strongly reachable from key.
      String key = new String ("key");
      /*
         Note: For this program, you cannot say String key = "key";. You
               cannot do that because (by itself), "key" is strongly 
               referenced from an internal constant pool data structure
               (that I will discuss in a future article). There is no
               way for the program to nullify that strong reference. As
               a result, that object will never be garbage collected,
               and the polling loop will be infinite.
      */
      // Create a ReferenceQueue object that is strongly reachable from q.
      ReferenceQueue q = new ReferenceQueue ();
      // Create a WeakReference object that is strongly reachable from wr.
      // The WeakReference object encapsulates the String object that is
      // referenced by key (so the String object is weakly-reachable from
      // the WeakReference object), and associates the ReferenceQueue
      // object, referenced by q, with the WeakReference object.
      WeakReference wr = new WeakReference (key, q);
      // Create an Object object that is strongly reachable from value.
      Object value = new Object ();
      // Create a Hashtable object that is strongly reachable from ht.
      Hashtable ht = new Hashtable ();
      // Place the WeakReference and Object objects in the hash table.
      ht.put (wr, value);
      // Remove the only strong reference to the String object.
      key = null;
      // Poll reference queue until WeakReference object arrives.
      Reference r;
      while ((r = q.poll ()) == null)
      {
         System.out.println ("Polling reference queue");
         // Suggest that the garbage collector should run.
         System.gc ();
      }
      // Using strong reference to the Reference object, remove the entry
      // from the Hashtable where the WeakReference object serves as that
      // entry's key.
      value = ht.remove (r);
      // Remove the strong reference to the Object object, so that object
      // is eligible for garbage collection. Although not necessary in this
      // program, because we are about to exit, imagine a continuously-
      // running program and that this code is in some kind of long-lasting
      // loop.
      value = null;
   }
}

Unlike SoftReferenceDemo, WeakReferenceDemo doesn't spend much time polling. After one call to System.gc ();, the garbage collector clears the weak reference to the String referent in the WeakReference object that wr references. Basically, WeakReferenceDemo works as follows:

  1. WeakReferenceDemo creates a String object that serves as a key. You must create that String object with a String constructor instead of simply assigning a String literal to key. The garbage collector will probably never collect String objects that you -- apparently -- create by assigning string literals to String reference variables. (A future article exploring strings will explain why.)
  2. Following a String's creation, WeakReferenceDemo creates a ReferenceQueue object and a WeakReference object that encapsulates String, which becomes the referent. We now have strong (via key) and weak (via the weak reference in the WeakReference object that wr references) references to String.
  3. WeakReference creates an Object value object that eventually associates with the String key object. A Hashtable object is created, and the table stores an entry consisting of the objects WeakReference and Object. (You will learn about the Hashtable data structure class in a future article.)
  4. WeakReference nullifies the String object's key reference, so the only String reference is the weak reference inside the WeakReference object.
  5. A polling loop allows us to wait for the garbage collector to clear the weak reference and place a strong reference to the WeakReference object on the reference queue. On my platform, only one System.gc (); call is necessary to request the garbage collector to run. The first time it runs, all weak references clear, and the next call to q.poll () returns a reference to WeakReference.
  6. After the loop, you can simply remove the WeakReference/Object entry from the hash table and nullify the Object's reference in value.
Note
To save yourself the trouble of automatically removing entries from a hash table, Java supplies the WeakHashMap class. Investigate that class in the SDK documentation and rewrite WeakReferenceDemo to use that class.

Phantom references

The phantomly reachable state manifests itself in Java through the PhantomReference class. When you initialize a PhantomReference object, you store a referent's reference in that object. The object contains a phantom reference to the referent, and the referent is phantomly reachable if there are no other references, apart from phantom references, to that referent. A phantomly reachable referent's finalize() method (assuming that method exists) has already run and the garbage collector is about to reclaim the referent's memory. A program receives notification, by way of a reference queue, when the referent becomes phantomly reachable, and subsequently performs post-finalization cleanup tasks that relate to the referent but do not involve the referent -- because there is no way for the program to access the referent.

Unlike SoftReference and WeakReference objects, you must create PhantomReference objects with a reference queue. When the garbage collector discovers that a PhantomReference object's referent is phantomly reachable, it appends a PhantomReference reference to the associated reference queue -- by calling Reference's enqueue() method. The following code fragment demonstrates the creation of a PhantomReference containing a String referent:

ReferenceQueue q = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (new String ("Test"), q);
1 2 3 Page 2
Page 2 of 3