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. 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.