HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Implementation of a fixed size pool of Objects in a distributed application must consider problems caused by the unpredictable nature of remote connections. An implementation is presented here for Java's Remote Method Invocation, which takes advantage of the Distributed Garbage Collector to solve those problems.

You're probably familiar with the mechanism of a fixed size pool of Objects, in the context of a memory management system, that keeps memory in a fixed size buffer pool. The idea is to manage the use of a scarce resource by requiring Objects to be checked out of and into a pool. When all Objects are checked out, the pool is empty; clients cannot check out any more, until a claimed one is checked back in. When a client is done with an Object, it is expected to check the Object back into the pool so that other clients may claim it.

Aside from memory management, you could use an Object Pool of this sort to manage any scarce resource, or limit the number of concurrent users of a service or data structure. For example, you could use an ObjectPool as the basis of a simple license enforcement scheme which would limit the number of concurrent users of a service. As another example, you could use it to limit the number of concurrent visitors in a chat room application.

In a simple, non-remote Java applet or application, implementation of a fixed size ObjectPool would be fairly straightforward, as long as you can follow the rule that clients must check objects back into the pool when they are done with them. The ObjectPool needs only to keep a fixed size Vector of Objects, and parcel them out as requested until no more are left.

The requirement that Objects be returned to the pool when a client is done with them could be problematic in large applications where the end of an Object's usefulness could occur in many different places, far removed from where it was checked out of the pool. The problem is analogous to memory management in systems without garbage collection. It quickly becomes onerous to live up to your responsibility of checking the memory, or in this case, the Object from the ObjectPool, back in.

One idea is to do garbage collection on Objects from the ObjectPool, leveraging off of Java's own garbage collection mechanism. You could override the finalize() method of the Objects in the ObjectPool to check the Object back in when the Object is slated for garbage collection. In this way, the client needs only to remove references to the Object and rely on the local garbage collector to check the Object back into the pool.

The problem with that solution is that garbage collection in Java is, with good reason, not guaranteed to be timely. Garbage collection is expensive; the VM is free to do it only when necessary. Since garbage collection of Objects from the ObjectPool would rely on Java's garbage collection, it would be subject to the same vagaries. In many applications, the ObjectPool cannot wait indefinitely to reclaim its lost Objects. If your program does not use much memory, your unreferenced Objects may never get garbage collected, and the Objects would never get checked back into the ObjectPool.

An additional concern arises in a distributed application where the client may be in a different VM. What if contact with the client is broken before the client is able to check its Object back in to the server's pool? How can the pool reclaim that lost Object?

I present here an elegant solution to these problems for Java's Remote Method Invocation, which relies on RMI's Distributed Garbage Collector. In contrast to the local garbage collector, the DGC's behavior is well defined and reliable with respect to its timing. The DGC employs a lease mechanism for remote references. All remote references are leased to clients for a default period of ten minutes (which may be overridden by setting the java.rmi.dgc.leaseValue property). The client VM must request a new lease before the period runs out. Otherwise the server considers the remote reference to be dead and releases the corresponding remote Object to the local garbage collector for potential collection.

RMI also provides the java.rmi.server.Unreferenced interface, which remote Objects may implement to be notified when all remote references to the remote Object have been released. This includes the case when the last remote reference to a remote Object is released due to expiration of the lease. So taken together, Distributed Garbage Collection and the Unreferenced interface, give us just what we need to reclaim lost Objects from an ObjectPool.

Listing 1 shows an implementation of an ObjectPool that works for both remote clients and local clients. Even non-remote applications could benefit from this implementation because of its reliable garbage collection and reclamation of unreferenced Objects.

There are a few subtleties in the implementation that are worthy of elaboration. First of all, notice that remote Objects are created only as needed. This avoids the overhead of pre-allocating the Objects which may not all be needed every time the application is run.

Remote Objects are never released for local garbage collection; they are reused. This avoids the overhead of constantly creating new remote Objects every time an Object needs to be doled out. RMI calls the unreferenced() method every time the last remote reference to the remote Object is released; even multiple times on the same remote Object.

When an Object is checked back in, notice that the corresponding Object is taken from the 'out' Vector rather than the returned Object itself. This is because the returned Object may be only the stub for the remote Object, rather than the remote Object itself. Vector's indexOf()method uses the equals() method to find the Object in the array. Since the PoolObject is extended from UnicastRemoteObject, it inherits the implementation of equals() from RemoteObject which considers stubs to be equal to their corresponding remote Objects. In this way, I'm guaranteed to be reusing the remote Object itself rather than just its stub.

One other note: The PoolObject inner class is a remote Object and, as such, needs to be post-compiled by rmic to create its stub and skeleton classes. Running rmic on inner classes is a bit tricky because the name of the inner class is generated by javac and includes the '$'character. The '$' character must be quoted to make it to the rmic compiler. On some platforms, it must be quoted more than once. For example, on Solaris, the command line looks something like:

rmic ObjectPool\\\$PoolObject

Anticipated Patterns of Reuse
The PoolObject in the ObjectPool has no substance aside from the unreferenced() method. This is not a very interesting Object aside from its characteristics as an Object in a fixed size pool. Even so, in some circumstances, that may be all you need. If so, you can use the ObjectPool as is, or perhaps write an ObjectPoolIfc Remote interface so that ObjectPool can be accessed directly from RMI clients.

It is more likely, though, that you will need more interesting Objects to be in the fixed size pool. The implementation in listing 1 is not just an example of how you could write your own ObjectPools. It is intended for you to reuse the class exactly as it is to create your own ObjectPools. It is probably worth elaborating on this as an illustration of general techniques for class reuse.

In the design of any RMI system, you will be faced with the choice of passing Objects between VMs by value or by reference. Passing by value is accomplished by implementing the Serializable interface, and passing by reference is accomplished by implementing the Remote interface. A discussion of the tradeoffs in this design decision is beyond the scope of this article. For our purposes here, let's consider how to reuse ObjectPool for both cases, when the Object in the ObjectPool is to be passed by value and by reference.

If the Objects in your fixed size pool are to be remote Objects, i.e. passed by reference, you can subclass PoolObject to create that Object and subclass ObjectPool, overriding the newPoolObject() factory method to instantiate your PoolObject subclass. This is an example of the Template Method design pattern. The code would look something like listing 2.

If the Objects in your fixed size pool are to be Serializable, i.e. passed by value, you can reuse ObjectPool by composition. The PoolObject remote reference will be passed to the client as a remote Object when the PoolObject is serialized. That contained remote reference in the Serializable PoolObject is not visible to clients of the PoolObject. It is there solely to enable collection of lost PoolObjects by the DGC. The code would look something like listing 3.

Of course these are just some of the ways that ObjectPool and PoolObject can be reused. This is a maximally reusable and generally powerful little class that should be a welcome addition to your object-oriented Java arsenal.

References
Gamma, E., Johnson, R. & Vlissides, J. "Design Patterns: Elements of Object-Oriented Architecture" Addison-Wesley, Reading, MA, 1995. Java Remote Method Invocation Specification, JavaSoft

About the Author
Steven Schwell is a Senior Developer and Java Guru in the New York office of Micromuse Inc., a leading provider of Service Level Management software. Steve is currently developing a number of large distributed Java apps. He holds an M.S. in Computer Science from Columbia University. Steve can be reached at [email protected]

	

Listing 1.
 
public class ObjectPool {  
 protected int size;  
 protected Vector in;  
 protected Vector out;  
 /* member class: */  
 public class PoolObject extends UnicastRemoteObject  
     implements Remote, Unreferenced {  
  public PoolObject() throws RemoteException { }  
  public void unreferenced() {  
   try { checkIn(this); // reclaim lost Object  
   } catch (Exception e) {}  
  }  
 }  

 /** constructor */  
 public ObjectPool(int size) {  
  this.size = size;  
  this.in = new Vector(size);  
  this.out = new Vector(size);  
 }  

 /** factory method to create new Object for the pool */  
 protected PoolObject newPoolObject() throws RemoteException {  
  return new PoolObject();  
 }  

 /** check out an Object from the pool */  
 public synchronized Object checkOut() {  
  Object o = null;  
  if (out.size() < size) {  
   if (in.isEmpty()) {  
    try {  
     o = newPoolObject();  
    }  
    catch (RemoteException e) {  
    }  
   }  
   else {  
    o = in.elementAt(0);  
    in.removeElementAt(0);  
   }  
   out.addElement(o);  
  }  
  return o;  
 }  

 /** check an Object back into the pool */  
 public synchronized void checkIn(Object o) {  
  int x = out.indexOf(o);   
   /* rely on Vector's use of equals() method  
   ** to equate stubs with remote objects if necessary  
   */  
  if (x < 0) throw new NoSuchElementException();  
  Object oo = out.elementAt(x);  
   /* get the real Object, not a stub */  
  out.removeElementAt(x);  
  in.addElement(oo);  
 }  
}  

Listing 2.
  
public XXPool extends ObjectPool {  
 class XXObject extends ObjectPool.PoolObject implements Remote {  
  ... // instance variables and methods for your Object  
  XXObject() throws RemoteException {}  
 }  
 public XXPool(int size) {  
  super(size);  
 }  
 protected PoolObject newPoolObject() throws RemoteException {  
  return new XXObject();  
 }  
}  

Listing 3.
  
public XXPool {  
 static public class XXObject implements Serializable {  
  private Object poolObject;  
  ... // instance variables and methods for your Object  
  XXObject(Object poolObject) {  
   this.poolObject = poolObject;  
  }  
 }  
 ObjectPool objectPool;  
 public XXPool(int size) {  
  this.objectPool = new ObjectPool(size);  
 }  
 public XXObject checkOut() {  
  Object o = objectPool.checkOut();  
  if (o == null) return null;  
  return new XXObject(o);  
 }  
 public void checkIn(XXObject o) {  
  objectPool.checkIn(o.poolObject);  
 }  
}
 
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.