|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 6 of 6
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.
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;
}
}
With this approach we can have objects that are reusable and, from the user's standpoint at least, immutable. We can also hide any state information we don't want to make visible to the users of the class. It also gives somewhat better performance than allocation and recycling, even for these simple objects, and substantially improves performance when used for composite objects.
If the pooled objects are only used by a single thread, a further performance enhancement is possible. The use of synchronized for the getInstance() and freeInstance() methods can be eliminated in this case, providing up to several times the performance of allocation and recycling. However,
this approach needs to be used with caution because of the resulting lack of thread safety in the pool.
One complication of the free pool approach to object reuse: the code using the objects needs to return them to the pool when usage is complete. In some ways, this looks like a retreat to the C/C++ approach of explicit allocation and deallocation. Unlike in C/C++, though, we can pick the particular cases in which we want to implement this level of object management. In cases in which we're dealing with high-usage object types, a strategic withdrawal to explicit allocation and deallocation can yield a major benefit in terms of improved performance.
This type of free pool is also much more forgiving than C/C++. If there are some abnormal cases in which the objects never get returned to the pool, the only impact will be somewhat lower performance -- the objects will eventually be garbage collected when they're no longer in use, and we'll just need to allocate new ones for the pool at some point. In C/C++, objects which are not deallocated stay around as long as the program executes, causing the memory leaks that can plague C/C++ programs.
Object pools are also often used for managing limited or costly resources, such as database connections. A number of past JavaWorld articles have covered this use in detail, and are linked in the Resources section below. Pools of this type are generally not so forgiving: if the resource is not properly freed, it may not be available for reuse without restarting the JVM.
In this article we've examined some of the issues surrounding object management in Java and provided techniques that can substantially reduce the volume of object creation and garbage collection in your programs. The timing results given in the article demonstrate the performance improvements possible with these techniques. These improvements come at the cost of a small increase in code complexity, but, in cases in which your programs are generating large numbers of objects, this increased complexity can have a big performance payoff.
As with all performance enhancement techniques, these need to be applied judiciously. Most of the code in any given application is not going to be used often enough for object creation to be a significant concern. Where it does become important is in code shown by design requirements or execution profiling to be heavily used. If performance is a concern, you need to look at the object-creation load this heavily used code generates and structure it to minimize the resulting object churn.
In future articles in this series, we'll look at other issues that can limit the performance of Java applications. The next article will take a look at the drawbacks of using type casts and illustrate coding techniques to reduce the amount of casting in your code. As a side benefit, this will also provide some useful utility classes for working with primitive types instead of the corresponding wrapper objects.