Improve the robustness and performance of your ObjectPool

Go beyond increased app speed and reduced memory requirements: Optimize and stabilize our object pooling utility

Object pooling is a simple and elegant way to conserve memory and enhance speed. By sharing and reusing objects, processes (threads) aren't bogged down by the instantiation and loading time of new objects, or the overhead of excessive garbage collection.

In our first article on object pooling, "Build your own ObjectPool in Java to boost app speed," I implemented a pool of database connections. In this article, I'll expand on that example, adding exception propagation and a clean-up thread.

Exception propagation

To make the object pool as transparent as possible, the borrow and/or get methods should throw the same exception(s) as the constructor of the desired object. Propagation of these exceptions is a simple matter of casting.

Starting from the bottom, the source of any exceptions will be found in the abstraction of the ObjectPool's create() method. Therefore, we simply add the throws Exception qualifier to its declaration.

abstract Object create() throws Exception;

In the JDBCConnectionPool abstraction of the create() method, a single call to DriverManger.getConnection() is made. This can throw an SQLException. Rather than catching the exception and throwing it away, as we did in the first article, we'll propagate it.

Object create() throws SQLException
{  
   return( DriverManager.getConnection( dsn, usr, pwd ) );
}

ObjectPool calls the create() method from checkOut(). Since create() now throws an exception, the checkOut() method must deal with it or pass it on. We'll do the latter by simply adding throws Exception to the declaration of checkOut().

synchronized Object checkOut() throws Exception

Now that the exception has reached the borrowConnection() method of JDBCConnectionPool, it's cast back to an SQLException and passed on to the process that requested the database connection.

public Connection borrowConnection() throws SQLException
{ 
   try
   {
      return( ( Connection ) super.checkOut() );
   }
   catch( Exception e )
   {
      throw( (SQLException) e );
   } 
}

The following blocks of code are now practically interchangeable, because they return the same object and throw the same exception:

Class.forName( ... ).newInstance();
Connection c = DriverManager.getConnection( ... );
JDBCConnectionPool cp = new JDBCConnectionPool( ... );
Connection c = cp.borrowConnection( ... );

The clean-up thread

As stated in the previous article, there is a condition in which the pool can accumulate a lot of objects that never get cleaned up. On top of that, it's rather inefficient to have the expiration logic in the checkOut() method. So we'll kill two birds with one stone (or thread, actually). We start by moving the expiration logic into a separate method called cleanUp(). Then we write a simple little thread that calls this method periodically.

Our thread needs only a pointer back to the object pool and a specified sleep time (passed in via the constructor).

class CleanUpThread extends Thread
{
   private ObjectPool pool;
   private long sleepTime;
   CleanUpThread( ObjectPool pool, long sleepTime )
   {
      this.pool = pool;
      this.sleepTime = sleepTime;
   }
   public void run()
   {
      while( true )
      {
         try
         {
            sleep( sleepTime );
         }
         catch( InterruptedException e )
         {
            // ignore it
         }         
         pool.cleanUp();
      }
   }
}

The thread is instantiated and started in ObjectPool's constructor.

cleaner = new CleanUpThread( this, expirationTime );
cleaner.start();

The new cleanUp() method, which gets called by the thread every expirationTime millisecond, simply releases all the old objects in the pool and asks the garbage collector to reclaim them.

synchronized void cleanUp()
{
   Object o;
   long now = System.currentTimeMillis();        
   Enumeration e = unlocked.keys();  
   while( e.hasMoreElements() )
   {
      o = e.nextElement();        
      if( ( now - ( ( Long ) unlocked.get( o ) ).longValue() ) >
expirationTime )
      {
         unlocked.remove( o );
         expire( o );
         o = null;
      }
   }
   System.gc();
}

Check out the full source for ObjectPool to see what checkOut() looks like without the expiration logic in it. The full source for JDBCConnectionPool is available as well.

Response to reader comments

Several readers had questions after reading the first object-pool article. I address the most relevant questions below.

How do I know when object pooling will benefit my situation?

If you have many threads running in parallel that create, use, and dispose a common object quite frequently, you should consider implementing a pool for that object -- especially if the instantiation and/or garbage collection of the object is an expensive operation. Database and socket connections are a great example.

What do I do about processes that check out objects and never return them?

This is a double-edged sword. At some point, you have to leave responsibility in the hands of the process that uses the pool; you can't make the pool completely idiot-proof...at least not yet.

If a process checks out an object and simply never checks it back in, there's nothing the pool can do about it. As long as the process has a reference to the object, it could use it at any time; any interference on the part of the pool could be catastrophic.

The next big release of Sun's JDK will include reference objects. These will allow the pool to detect (via some wrapper-class implementation) when a checked-out object has been "freed" by the process so the pool can reclaim the object. As an interim solution, if the pool detects that an object has been checked out for an abnormal amount of time (as defined by you, the programmer), the pool can simply release its reference to the object. Thus, if and when the process releases its reference, the object will be garbage collected.

How can I limit the size of the pool?

I strongly discourage limiting the size of the pool. Besides defeating the whole purpose of the pool, limiting its size will open the doors to a loss in performance and the possibility of some nasty deadlocks. However, if you really must, you can put some size-checking logic in checkOut() to make sure it doesn't create more copies than desired. Then, also within checkOut(), you will have to utilize the wait() and notify() methods of Thread to ensure that a call to the pool does not return until an object is available.

Conclusion

With exception propagation and the clean-up thread, the ObjectPool class becomes more robust and one step closer to being transparent. I hope this tool proves as useful for you as it has been to me. As always, I welcome your comments, criticisms, and design-improvement suggestions.

Thomas E. Davis is a Sun Certified Java Programmer. Although he was weaned on Linux boxes and Perl code (and misses them dearly), he currently pays his rent by developing Java back-end services on Windows NT.

Learn more about this topic

  • The first JavaWorld article on object pooling in this two-part series, Build your own ObjectPool in Java to boost app speed http://www.javaworld.com/javaworld/jw-06-1998/jw-06-object-pool.html
  • JDBC information at Sun http://java.sun.com/products/jdk/1.1/docs/guide/jdbc/index.html
  • Information about threads and reference objects at Sun http://java.sun.com/products/jdk/1.2/docs/guide/refobs/index.html
  • Garbage collection information from Sun's online Java tutorial http://java.sun.com/docs/books/tutorial/java/javaOO/garbagecollection.html
  • Download the complete source, which includes the core object pooling class and an example implementation using database connections http://www.javaworld.com/jw-08-1998/objectpool/objectpool.zip