Manage memory effectively with Java reference objects

Java's automatic garbage collection is something of a mixed blessing: Although it reclaims memory of unused objects, it can take away some of the programmer's freedom. Discover how to exercise a little control over memory usage with reference objects.

Ask any Java programmer to name the four best features of the Java language, and he’ll probably say something about automatic garbage collection—the process the JVM uses to detect objects that are no longer in use and reclaim their memory. Java’s garbage collection eliminates the potential for a number of common bugs related to memory management, which are at the same time serious and hard to debug. Accessing objects that have already been released and failing to release objects that are no longer in use simply aren’t concerns for Java programmers.

On the downside, automatic garbage collection does take some of the programmer’s freedom away, because it’s not possible to know when a given object will be collected, and there's no control over memory management. In Java 1.2, a new type of object, called a reference object, was introduced to provide a way around these limitations. Before I discuss these reference objects, let's learn a bit about Java’s garbage collection process.

Garbage collection in Java
Simply put, garbage collection is the process of freeing memory that is no longer in use by an application—taking out the garbage, if you will. The garbage collector is the entity responsible for doing this in Java. It deems objects as garbage when they are no longer in use by the application.

How does the garbage collector make that determination? Formally speaking, objects that aren't in use are all objects that cannot be reached, directly or indirectly, from any active thread. Any such objects are eligible to be collected and their memory reclaimed and freed.

When and how garbage collection occurs depends on the garbage collector’s implementation. There are a number of accepted collection strategies and the Java Language Specification leaves the choice of strategy up to each JVM implementation. The only guarantee is that before reclaiming an object’s memory, the object’s finalize method is required to be called, which it will inherit from Object if it doesn’t define one on its own. This leaves an opportunity for the object to do any cleanup actions that may be necessary.

Take a look at the Java Language Specification, section 12.6 for a formal, detailed description of Java’s garbage collection.

What are reference objects?
Reference objects are a special type of object for holding references to other objects in such a way that the garbage collector can still reclaim the referenced object's memory—this is also known as a weak reference. Reference objects allow a program to keep references to objects that can be reclaimed if needed, as they do under low memory conditions.

Reference objects are represented by the classes SoftReference and WeakReference, found in the package java.lang.ref. The chief difference between these two classes is that the garbage collector uses different algorithms to decide when to reclaim the objects they refer to. For all practical matters, they can be used interchangeably, so I will continue to use the term reference object to mean either of these classes.

To create a weak reference to an object, you pass that object to the reference object’s constructor. Once created, a reference object’s weak reference is immutable: It cannot be changed, except by clearing the reference by calling the clear method.

A reference object’s get method returns the object passed to the reference object’s constructor, or it returns null if the clear method has been called. When an object is accessible only through weak references, it becomes eligible for garbage collection. When the garbage collector decides to reclaim the memory for a weakly referenced object, it first clears all weak references to it held by reference objects, so that calling get will return null, just as if you’d called clear yourself.

Refer to this example
The best way to understand reference objects is by seeing them at work, so refer to Listing A. Here we have a class called MemoryBlock that has one purpose in life: to consume memory. Each MemoryBlock has an ID (so that I can identify it), and a size, which is roughly the amount of memory it occupies. The MemoryBlock class also prints a message when it is created and when it is finalized, so that I can monitor its life cycle.

In Listing B, I’ve put the code for a simple example that uses MemoryBlock to repeatedly allocate blocks of memory, each block twice as large as the last, starting at 64 KB. Each MemoryBlock is saved to an ArrayList, which is printed at each iteration of an infinite loop.

The results of running this example are fairly predictable. On my test machine, after about nine blocks, the program runs out of memory, as you can see in Figure A.

Figure A
On my test machine, this example runs out of memory.

The important thing to notice in Figure A is that since the MemoryBlocks were stored in an ArrayList, the garbage collector could not reclaim them to free the memory they occupied. That’s why you don’t see any messages indicating that MemoryBlock.finalize was called.

Now take a look at Listing C, where the MemoryBlocks are saved to the ArrayList through instances of the MyReference class, found in Listing D. MyReference extends SoftReference and overrides the toString method to return a string representation of the object to which the class holds a weak reference.

Figure B shows the output of the modified example. The program still runs out of memory after approximately the same number of iterations, but since the only references to the MemoryBlocks are through reference objects, they can be reclaimed by the garbage collector when memory runs low.

Figure B
Still runs out of memory, but garbage collections are evident

As you can see from the repeated finalize messages, old blocks are reclaimed as new ones are created. The reference objects that refer to the finalized MemoryBlocks are cleared as well. That’s why their corresponding ArrayList positions print as null. Note that the  MyReference instances are still there; they simply don't point to the MemoryBlocks anymore, because these have been garbage-collected.

Much more control
As I've illustrated, reference objects provide much better control of the garbage collection process than is possible without them. By using them, you’ll still retain the benefits of automatic garbage collection, namely ease of use and safety.


Editor's Picks