Java performance programming, Part 1: Smart object-management saves the day

Learn how to reduce program overhead and improve performance by controlling object creation and garbage collection

1 2 3 Page 2
Page 2 of 3
    ...
    // Allocate dedicated time formatting objects as member variables.
    private final Date convertDate = new Date();
    private final DateFormat convertFormat = DateFormat.getDateInstance();
    private final StringBuffer convertBuffer = new StringBuffer();
    private final FieldPosition convertField = new FieldPosition(0);
    ...
        // Get the default string for time.
        long time = ...;
        convertDate.setTime(time);
        convertBuffer.setLength(0);
        StringBuffer output =
            dateFormatter.format(convertDate, convertBuffer, convertField);
        String display = output.toString();
        ...

This code is considerably longer than the original statement, but executes much more quickly because the only object constructed each time through is the output string. In a test run with 100,000 iterations, this code took only 8 seconds to execute, as opposed to 50 seconds for the original technique. This approach does incur some additional memory usage as the price of the speed advantage, since the objects used for the formatting are kept permanently allocated instead of being freed when not in use. But if the code is executed frequently, it's a very good trade-off.

It's worth pointing out that the same technique applies if you have an inner loop within a method doing a large number of iterations. You may not need to have a set of the objects used within the loop permanently owned by the object containing the method, but you can still move the object allocations outside the loop so that they're only done once. In this case, our example code might look like this:

        // Allocate objects to be used inside loop.
        Date date = new Date();
        DateFormat formatter = DateFormat.getDateInstance();
        StringBuffer buffer= new StringBuffer();
        FieldPosition field = new FieldPosition(0);
        // Execute the loop.
        for (...) {
            // Get the default string for time.
            long time = ...;
            date.setTime(time);
            buffer.setLength(0);
            StringBuffer output = formatter.format(date, buffer, field);
            String display = output.toString();
        }

This dedicated object reuse technique often works especially well in combination with the approach of using primitive values in place of objects, as described above. A dedicated object can be initialized from the primitive values and passed to class library methods that expect an object of the original type. The use of the dedicated Date object above provides an example of this.

Multithreading owned objects

If we have a set of owned objects, and multiple threads can execute the code that uses the objects concurrently, we need to prevent conflicts between the different threads' usage of the objects. The easiest way to accomplish this is to designate one of the objects as the lock for the whole set, and enclose the code using the set of objects within a synchronized block on the lock object. This adds the overhead of a locking operation for each use of the owned objects, but the locking overhead is low in comparison to the object creation time.

In this case, suppose we made the convertDate object the lock. The code fragment using the objects would then need to be changed to the following:

        // Get usage of the owned objects.
        synchronized (convertDate) {
            // Get the default string for time.
            long time = ...;
            convertDate.setTime(time);
            convertBuffer.setLength(0);
            StringBuffer output = 
                dateFormatter.format(convertDate, convertBuffer, convertField);
            String display = output.toString();
        }

If the code using the owned objects is required to be single threaded, there's no need to bother with the locking step. However, adding the locking can give some additional flexibility.

For instance, the example we've been following uses instance variables for the owned objects, so that there's one set of objects for each instance of the containing class. This approach makes sense when there's heavy use of the owned objects, or when the objects may need to be configured differently for each instance of the containing class. This would be the case, for example, if we wanted to have the format string for our date example specified to the constructor of the class, instead of always using the default format.

In cases where usage of the owned objects is not extremely heavy, and they don't need to be customized for each instance of the containing class, it may be better to have them owned by the class itself rather than have a copy for every instance of the class. To do this, just make the member variables static:

    ...
    // Allocate dedicated time formatting objects as member variables.
    // (Synchronize on convertDate to use any or all of the objects.)
    private static final Date convertDate = new Date();
    private static final DateFormat convertFormat = DateFormat.getDateInstance();
    private static final StringBuffer convertBuffer = new StringBuffer();
    private static final FieldPosition convertField = new FieldPosition(0);
    ...
        // Get usage of the owned objects.
        synchronized (convertDate) {
            // Get the default string for time.
            long time = ...;
            convertDate.setTime(time);
            convertBuffer.setLength(0);
            StringBuffer output =
                dateFormatter.format(convertDate, convertBuffer, convertField);
            String display = output.toString();
        }
        ...

This approach gives the speed advantage of dedicated object reuse while sharing the memory overhead across all instances of the class.

Pooled object reuse

Free object pools represent another form of object reuse. With the free pool approach, code using objects of the pooled type must track usage and explicitly return the objects to the free pool when usage is complete. The free pool keeps a collection of objects available for reuse, adding returned objects to the collection. When an object is needed, the free pool removes one from the available collection and reinitializes it, rather than constructing a new object. The free pool only constructs a new object of the pooled type when the available collection is empty.

The bookkeeping overhead of maintaining a collection of available objects limits the performance gain of this approach, but it can still be useful in circumstances where there's a lot of reuse of a particular object type. We'll look at alternative approaches to handling this bookkeeping and see how each performs in practice in order to get a better idea of when each approach might be useful.

Everybody into the pool

The basic code for constructing and managing a free pool can be implemented in a number of ways. The most flexible approach (though not necessarily a good one from the performance standpoint, as we'll see) uses a passed-in type for the objects held by the pool, and could be structured as follows:

import java.lang.*;
import java.util.*;
public class ObjectPool
{
    private final Class objectType;
    private final Vector freeStack;
    
    public ObjectPool(Class type) {
        objectType = type;
        freeStack = new Vector();
    }
    
    public ObjectPool(Class type, int size) {
        objectType = type;
        freeStack = new Vector(size);
    }
    
    public synchronized Object getInstance() {
        
        // Check if the pool is empty.
        if (freeStack.isEmpty()) {
            
            // Create a new object if so.
            try {
                return objectType.newInstance();
            } catch (InstantiationException ex) {}
            catch (IllegalAccessException ex) {}
            
            // Throw unchecked exception for error in pool configuration.
            throw new RuntimeException("exception creating new instance for pool");
            
        } else {
            
            // Remove object from end of free pool.
            Object result = freeStack.lastElement();
            freeStack.setSize(freeStack.size() - 1);
            return result;
        }
    }
    
    public synchronized void freeInstance(Object obj) {
        
        // Make sure the object is of the correct type.
        if (objectType.isInstance(obj)) {
            freeStack.addElement(obj);
        } else {
            throw new IllegalArgumentException("argument type invalid for pool");
        }
    }
}

This code uses a member java.util.Vector as a growable stack to implement the free pool. It requires that an object class be specified in the constructor (with an optional pool size estimate), and checks that only the proper type of objects are added to the pool. It also automatically creates and returns a new instance of the pooled object class if none are present in the pool.

To see how this might operate, suppose we're working with some graphics code that makes extensive use of coordinate rectangles. Using the base java.awt.Rectangle class makes sense, but constantly allocating objects of this type for short-term uses might add a lot of overhead. In this case, we could easily use the ObjectPool class to eliminate this allocation overhead:

    // Construct a shared pool of Rectangle objects.
    private static final ObjectPool rectanglePool = new ObjectPool(Rectangle);
    ...
        // Construct a rectangle to be passed.
        Rectangle rect = (Rectangle) rectanglePool.getInstance();
        rect.height = height;
        rect.width = width;
        rect.x = x;
        rect.y = y;
        ...
        // Return passed rectangle to pool.
        rectanglePool.freeInstance(rect);
        ...

This is convenient and easy to implement. Unfortunately, it's also slower than just allocating the Rectangle object directly! All the generic code (especially the extensive use of type casts), along with the synchronization of the Vector class, adds so much overhead to the bookkeeping that a test run with this type of pool actually took twice as long as directly allocating and discarding the objects.

The test was somewhat biased in favor of the allocation and discard approach, since it used short-lived Rectangle instances and a bare minimum of other objects in the program (thereby letting garbage collection run at its best), but it demonstrated that this object-pool approach will give minimal gains in performance at best, and a loss in performance at worse. The generic approach can work fine for object pools controlling resources (such as database connections), but we need something faster for reducing allocation overhead.

A built-in pool

The generic ObjectPool approach tracks free objects, but adds so much overhead in the bookkeeping that it eliminates any advantage we might have from reusing objects rather than reallocating. As we'll discuss in more detail in the next article in this series, this can be a common problem with generic code -- it provides code reuse, but generally with a performance penalty. The solution to this type of overhead is to change to type-specific code, and that's what we'll look at for our next shot at an object pooling mechanism.

While we're at it, we can also eliminate another problem with the generic approach. A generic pool requires access to the internal state variables of the managed objects, so that the objects can be initialized for reuse. If we want to hide state information or make it immutable (so that it can't be modified once it's been initialized for a particular usage) in our objects, we need a different approach.

We can solve both these problems by building the object pool into the actual object class, rather than making it a separate add-on. To see how this works, let's define our own immutable Rectangle equivalent with a built-in pool (which we'll make fixed size, for simplicity):

import java.awt.*;
import java.lang.*;
import java.util.*;
public class ImmutableRectangle
{
    private static final int FREE_POOL_SIZE = 40;  // Free pool capacity.
    // Pool owned by class.
    private static final ImmutableRectangle[] freeStack =
        new ImmutableRectangle[FREE_POOL_SIZE];
    private static int countFree;
    // Member variables for state.
    private int xValue;
    private int yValue;
    private int widthValue;
    private int heightValue;
    
    private ImmutableRectangle() {
    }
    
    public static synchronized
     ImmutableRectangle getInstance(int x, int y, int width, int height) {
        
        // Check if the pool is empty.
        ImmutableRectangle result;
        if (countFree == 0) {
            
            // Create a new object if so.
            result = new ImmutableRectangle();
            
        } else {
            
            // Remove object from end of free pool.
            result = freeStack[--countFree];
        }
        // Initialize the object to the specified state.
        result.xValue = x;
        result.yValue = y;
        result.widthValue = width;
        result.heightValue = height;
        return result;
    }
    public static ImmutableRectangle getInstance(int width, int height) {
        return getInstance(0, 0, width, height);
    }
    
    public static ImmutableRectangle getInstance(Point p, Dimension d) {
        return getInstance(p.x, p.y, d.width, d.height);
    }
    public static ImmutableRectangle getInstance() {
        return getInstance(0, 0, 0, 0);
    }
    public static synchronized void freeInstance(ImmutableRectangle rect) {
        if (countFree < FREE_POOL_SIZE) {
            freeStack[countFree++] = rect;
        }
    }
    public int getX() {
        return xValue;
    }
    public int getY() {
        return yValue;
    }
    public int getWidth() {
        return widthValue;
    }
    public int getHeight() {
        return heightValue;
    }
}
Related:
1 2 3 Page 2
Page 2 of 3