Remote object activation

  Few deny that, today, RMI is among the most useful and versatile technologies within the Java pantheon. Arguably, this has not always been the case, as there were some glaring lacunae within the RMI architecture implemented under earlier versions of Java. Under JDK 1.1, every RMI server had to be started manually prior to its usage by remote clients. Also, if a remote object crashed during operation, a client could not really resume "where it left off," as the previous state of the remote object was unrecoverable. But all that is history. I will examine the details behind a significant enhancement to the RMI architecture under the Java 2 platform that helps overcome these limitations.

Understanding Remote Object Activation
While it sounds simple, starting remote servers prior to their usage may not always be a practical proposition. For instance, it is highly inefficient to have hundreds of resource-intensive remote objects running in memory all the time—especially if they are used only on an occasional basis. What's worse, they may even turn out to be a logistical nightmare if your enterprise's distributed computing environment demands hundreds or thousands of remote objects. As we can see quite clearly, the old approach of prestarting an RMI server becomes unmanageable rather quickly in a real-life scenario.

Luckily for us, however, with the advent of Java 2, the headaches associated with developing fairly large-scale distributed systems using RMI have now been greatly alleviated. Specifically, with the help of the new Remote Object Activation (ROA) feature, you no longer have to worry about starting up remote objects prior to their invocation.

By extending java.rmi.activation.Activatable and making use of the new RMI activation daemon rmid, RMI developers can now register information about remote object implementations that should be instantiated only when necessary, rather than running all the time. The ROA mechanism accomplishes this by what is known as lazy activation. Lazy activation implies that the passive remote object is activated by rmid, by loading its constituent classes into a JVM only on the receipt of the client's first remote method invocation, and not until then. rmid—which runs on the same host as the activatable object and the RMI name server, rmiregistry—also needs to be configured with information that will allow it to locate and load the classes used by the remote object. This task is usually delegated to a separate setup or bootstrap program, after first starting up rmid and rmiregistry. Activatable objects can optionally be associated with a persistent state. This can be provided to the remote object during activation to reestablish its state information. The good news is, choosing to make your remote objects is strictly a server-side decision; RMI clients don't care whether the remote objects are activatable or not.

Developing an Activatable Remote Object ROA can be a fairly complex topic for the novice RMI developer. If you are not familiar with Java remote objects, it may be useful for you to read my earlier article on distributed Java and RMI. 1 Assuming you are familiar with the basics of RMI, let us take a closer look at activatable remote objects. The best way to understand ROA is to see it in action via a concrete example. As with any RMI object, the activatable object in our example implements a remote interface. Here, CountInterface (shown in Listing 1), contains a single remote method getCount() and basically allows a client to obtain a count of the number of times the remote object has been accessed.

The class for our activatable server object is shown in Listing 2. CountServer not only maintains a running count of the number of accesses by RMI clients, but is also activatable—i.e., its instantiation is deferred until the first remote method invocation. Further, it also makes use of MarshalledObject to regain its state—even if it is restarted after a server shutdown.

Now, let us dissect the activatable remote server a little and see how it differs from a conventional RMI server. Consider the line:

public class CountServer extends Activatable

 implements CountInterface
Unlike regular remote objects that subclass java.rmi.server.UnicastRemoteObject, the class for an activatable object usually needs to extend java.rmi.activation.Activatable. (I emphasize usually, because you can also create an activatable server object by exporting a remote object using the static method Activatable.exportObject().)

Observe the constructor for CountServer:

public CountServer(
  ActivationID id, MarshalledObject data)
	throws RemoteException,
     ClassNotFoundException, IOException {
...
}
The constructor is automatically invoked by the RMI activation daemon rmid whenever a new instance of this remote object needs to be created, and really serves two purposes. It first registers the remote object with the activation system, and then exports it on an anonymous port. The ActivationID passed in by rmid represents a unique identifier for the remote object to be activated and includes a remote reference to the object's activator.

The MarshalledObject parameter here can also optionally contain any serialized object. In this example, we use it to supply the name of the file containing some previously persisted state information. The great thing about using the MarshalledObject is that now, your remote objects can preserve state even between successive restarts. Observe that the constructor also checks whether the file passed via MarshalledObject exists, indicating the presence of a previously persisted state. If it does exist, the method restoreState() is invoked, causing the activatable object to initialize itself from the persisted data.

Because our remote object implements CountInterface, it defines getCount(), which increments a counter specifying the number of times the object was accessed. For each client invocation, this method updates and saves the count data to the file that was passed as the MarshalledObject, before returning the count to the client.

You may also have noticed that unlike a conventional RMI server, an activatable remote object does not contain a main() method. There is no need for one, as the server is now instantiated by rmid, instead of being manually started from the command line.

Setup Program
Before activatable objects can be instantiated, the rmid and rmiregistry services must be properly initialized with information regarding these objects. First, we need to initialize rmid with some URL-based information that will allow it to locate and load the remote object's constituent classes before instantiating them. Second, we have to register a remote reference (an instance of the generated stub class) for the activatable class within the rmiregistry, and associate it with an URL-based identifier. A setup program that is executed for each activatable object usually performs this configuration work. Listing 3 demonstrates the setup program for our activatable remote object.

Because our activatable object serializes its state information to a file, we have to explicitly grant this level of access, bypassing the default security sandbox. For testing, you can give carte blanche access to the setup program by disabling the security mechanism with the following policy file:

grant {
	permission java.security.AllPermission;
};
Once we have created the policy file, we need to initialize the java.security.policy property with its location. For example:
props.put("java.security.policy","/policy");
assumes that the policy file exists in the root directory (i.e., C:\ on win32 systems).

The activation system provides great flexibility when activating remote objects through the use of activation groups. Each activation group is associated with its own JVM. Thus, it is possible to run groups of activatable objects within different JVMs by associating them with a distinct ActivationGroupID:

ActivationGroupDesc.CommandEnvironment ace =
  null;

ActivationGroupID agi=
	    ActivationGroup.getSystem().registerGroup(
                             new
ActivationGroupDesc(props, ace));
Consider the line:
MarshalledObject data=
  new MarshalledObject(
  new File("/Count.ser"));
Here, MarshalledObject is indicated as being the file Count.ser, which is created in the root directory. This file is then used by our activatable remote object to persist the updated count data after each client invocation. Although we could have probably hard-coded the filename within the remote object itself, MarshalledObject provides a flexible mechanism for passing any kind of initialization object to the activatable remote object.

All the information required to activate a remote object is stored within an instance of the activation descriptor ActivationDesc. This activation descriptor is then interrogated by rmid to obtain the necessary information during object activation. The line:

      ActivationDesc desc = new ActivationDesc(
      agi, "cup.chap6.activation.CountServer",
      location, data);
creates an ActivationDesc object that stipulates the fully qualified class name for the activatable remote object, the location of its code, the object's group identifier, as well as any object-specific initialization data. The activatable object is then registered with rmid by passing the activation descriptor as a parameter to the static method Activatable.register() as:
     
counter =
      (CountInterface)Activatable.register(desc);
The register() invocation returns the Remote stub or client-side proxy for the activatable object. Basic RMI mechanics dictate that the client must obtain a handle to the remote object by performing a lookup on the rmiregistry. Thus, we bind the stub for the remote object within rmiregistry and permit its access via an URL-based name through:

	Naming.rebind("/CountServer", counter);
The Client Applet
Listing 4 shows the client applet which is used to access the activatable remote object. Notice that there is nothing new happening here—the RMI client remains the same for a regular remote object as well as for an activatable one.

Demonstrating Remote Object Activation
Each time a client makes a remote method invocation, the activatable remote object updates its counter and stores the state in the form of serialized data to the file specified by the MarshalledObject. Now that you have the requisite example programs ready, you can compile them and generate the RMI stub and skeleton files as usual:

javac -d . *.java
rmic -d . com.jr.active.CountServer
Before you can run the setup program, you need to start the RMI name server rmiregistry and the activation daemon rmid:
start rmid
start rmiregistry
Assuming that you had compiled the example source files in the path c:\examples and placed the security policy file in the root directory, you can execute the setup program as:
start java -Djava.security.policy=\policy
     -Djava.rmi.server.codebase=file:\examples        com.jr.active.BootStrap
The counter.html file shown in Listing 5 should work fine for the purpose of loading your applet.

The RMI applet can then be started as:

start appletviewer counter.html
Note that you do not have to first start up the remote server. The activatable object was already registered within the registry when we ran the setup program. If you keep executing CounterApplet repeatedly for a minute or two, you should eventually see the output shown in Figure 1.

Figure 1

Figure 1. Output of repeated execution of CounterApplet.

Now let us observe an interesting phenomenon. Without closing the appletviewer window, shut down rmiregistry and rmid. After that, restart both of them, and run the setup program as before. You should observe that the applet can resume where it left off—i.e., the RMI stub reference holds well even if the server goes down. Also, the activation system automatic- ally restarts the remote server, while reading in the previous state from the serialized file passed via MarshalledObject.

Design Considerations
Remote object activation is an extremely powerful feature and should empower us to build much more flexible distributed systems. Although activatable and non-activatable remote objects can coexist in the same environment, they introduce a new level of complexity to the remote object lifecycle management issues. For instance, activatable remote objects can be deactivated via the method Activatable.unexportObject(). If the target remote object happens to be the last one present within its activation group, then one of the consequences of this deactivation call would result in the shutting down of the JVM! This could in turn lead to RMI clients that access non-activatable remote objects left with dangling stub references.

Conclusion
Using the Activatable class and the activation daemon rmid, you can design remote object implementation that can be executed on-demand, rather than running all the time. This feature allows us to design highly scalable distributed systems while making efficient use of system resources. rmid provides a default JVM from which other JVM instances may be automatically spawned. We have also seen how the MarshalledObject can be used for passing initialization data through the activation descriptor, without having to hard-code these values within the remote object itself.

Reference
1. Seshadri, G., "Distributed Java Computing Using RMI," Java Report,
Vol. 2, No. 10, Oct. 1997, pp. 39-46.

Featured

Upcoming Events

AppTrends

Sign up for our newsletter.

I agree to this site's Privacy Policy.