1. Java Distributed Object Model
RMI and Distributed Computing Applications:
Locate Remote Objects: Applications can locate remote objects
in one of two ways: Either register the remote objects with RMI's simple
naming registry called "rmiregistry" and use methods from the class java.rmi.Naming
to lookup, bind, rebind and unbind OR pass/return a reference of the remote
object as a part of the application's normal operation.
Communicate with Remote Objects: RMI provides the mechanism
for communication between the client and the server; to the programmer
everything is like any other java method invocation.
Load Class bytecodes for objects that are passed or returned: RMI
allows java objects to be passed/returned, and provides necessary mechanisms
to load the object's code and transmit its data. This mechanism may use
already existing Web Server on the client and the server sides.
The Following diagram shows a distributed application using RMI. Pre-existing web-servers are used to load class bytecodes.
Differences between Java Distributed Object Model and Java Object Model:
The Java distributed object model is similar to the Java object model in the following ways:
java.rmi.Remote interface
It list a number of remote methods that can be invoked by a remote
virtual machine. It has the following features:
rmi server
The Rmi server functions are provided by java.rmi.server.RemoteObject
and its subclasses : java.rmi.server.RemoteServer, java.rmi.server.UnicastRemoteObject,
java.rmi.activation.Activatable.
Class Annotation: When an object is sent from one VM to another in a remote method call, the RMI system annotates the class descriptor in the call stream with information (the URL) of the class so that the class can be loaded at the receiver. It is a requirement that classes be downloaded on demand during remote method invocation.
Parameter Transmission: RMI serializes its parameters by using
a subclass of ObjectOutputStream. The subclass overrides the replaceObject
method. All objects are written by calling the writeObject method. The
replaceObject is called to each of the written object. The replaceObject
returns the stub of the object if the object was remote else it returns
the object itself. The subclass also implements the annotateClass() method
which annotates the call stream with location of the class. All other default
behavior of ObjectOutputStream are maintained.
The same mechanism applies for return values and exception transmission.
While unmarshalling a subclass of ObjectInputStream is used. It overrides
the replaceObject method. The method does the necessary task of dynamic
class loading, if required, by using the class annotation sent in the marshalled
object.
Stub and Skeleton
Stub: is the client's local representative or proxy for the remote object. The caller invokes method on the local stub which is responsible for carrying out the method call on the remote object. When a stub method is called the stub does the following activities:
Thread and RMI
RMI does not guarantee a one-one mapping of a remote method invocation
to a thread. As methods of the same remote object may be executing concurrently,
the methods must be made thread-safe.
Distributed Garbage Collection
Distributed garbage collector uses references counting for garbage
collection. A reference count of all live references is maintained in the
java virtual machine. Whenever a live reference enter the JVM, the reference
count is incremented. When the first live reference to an object is made
a "referenced" message is sent to the server. As live references are unreferenced
the count is decremented and when there are no live references a "unreferenced"
message is sent. When the remote object is no longer referenced by any
client, a weak reference is maintained. The distributed GC can call the
local GC to ensure that no local references exists and can then garbage
collect the remote object.
If there is a network partition between the client and remote server there is possibility of premature collection (the server believes that the client has crashed) and hence remote references cannot guarantee referential integrity.
Clients Accessing Through Firewalls:
The RMI transport layer by default tries to make a direct socket connection
between the client and the server. If the client is under a firewall protection
then, this is not possible. The alternative is to go through firewall-trusted
HTTP protocol.
The transport layer embeds the RMI call as a HTTP POST request. The return value/exception of the remote call comes in as the body of the response. The HTTP post is formulated as:
java.rmi.RemoteException : The embedded I/O Exception of a RemoteException can be obtained by accessing the public detail field if the RemoteException class. It is of type Throwable.
java.rmi.Naming : provides methods for storing and obtaining
references to remote objects in the remote object registry.
The methods has a string arguments of the form: //host:port/name
.
Here, host is the host where the registry is located and port
is the port number on which the registry accepts calls, name is uninterpreted
by the registry. host and port are optional in which case
they defaults to localhost and 1099 ( the port rmiregistry uses).
A registry can be shared by all servers in a host (by using rmiregistry) or a server can have its own registry by calling java.rmi.registry.LocateRegistry.createRegistry method.
Important methods of java.rmi.Naming class includes: bind, rebind, unbind,
lookup, list. This methods can throw java.rmi.AccessException at any time.
java.rmi.server.RemoteObject: Abstract class
An abstract class that gives implementation of java.lang.Object methods
-- hashCode, equals and toString for remote objects. equals checks
on references equality.
The remoteObjects embeds a remote reference as a field. It has the
toStub(remote) methods that can return the stub of a remote object that
was already exported.
Serialization: the remoteObject implements the writeObject()
and readObject() methods of serializable interface.
java.rmi.server.RemoteServer: Abstract classwriteObject method:
- if the ref member is null a java.rmi.MarshallException is thrown
- if ref is not null then ref.getRefClass is called
- if the ref class in not null then
- the name of the ref class is written as a UTF string
- the writeExternal method of ref is called to write the data of ref to the output stream
- else if ref class was null then
- an empty UTF string is written
- the data of ref is serialized into the output stream
readObject method:
- read the class name of the object from the input stream
- if the class name is null
- an object is read in from the stream and ref is initialized to it
- else
- the full name of the class name is constructed and an instance of it is created
- the instance is initialized by reading in the external form from the input stream
java.rmi.UnicastRemoteObject: class
Features:
Serializing UnicastRemoteObjects:
Information contained in uniCastRemoteObjects are transient and hence
are not serialized to any outputStream. By informations of subclasses will
be written to outputStream. When a UnicastRemoteException object is deserialized
from an inputStream the object is automatically exported.
Unexporting a UnicastRemoteObject:
unexportObject method of UnicastRemoteObject can be called to unexport
a remote object. If the boolean argument to this methods is true then the
remote object is forcibly unexported even if there are some pending calls.
Clone:
UnicastRemoteObject does not implement the Cloneable interface and
hence in itself cannot be cloned. But it defines the clone method so that
if a subclass implements the cloneable interface, the parent part is also
cloned correctly.
java.rmi.server.Unreferenced interface
Server remote objects that implement this interface has the unreferenced
method invoked when there is no references to the remote object. This method
could be called many times in a lifetime of the remote object
java.rmi.RMISecurityManager class
It has the same security restriction as java.lang.SecurityManager except
that it overrides the checkPackageAccess method. In RMI, if a security
manager is not provided only stub and classes loaded from the local classpath
are allowed. This allows for protection against classes downloaded as a
result of remote method invocation.
java.rmi.server.RMIClassLoader class
Provides static methods that RMI uses internally to download class
bytecodes for parameters and return types.
RMI Socket Factories:
The new JDK1.2 RMI provides 2 interfaces java.rmi.server.RMIClientSocketFactory
and java.rmi.server.RMIServerSocketFactory that allows customizes socket
and serverSocket to be used in the new RMI wire. They are specified as
arguments to the new constructors and exportObject methods of UnicastRemoteObject.
The implementation of RemoteRef and ServerRef used in stubs and skeletons
for remote object exported are the new classes -- UnicastRef2 and UnicastServerRef2.
The endpoint of contact represented in UnicastRef is different from that
of UnicastRef2. In UnicastRef it is simply the hostname in UTF followed
by the port number. Whereas in UncastRef2 it is a format byte (that says
what the content is) followed by the UTF string of the host, port number
and an optional serialized representation of the RMIClientSocketFactory
that the client uses to establish connection to the server (i.e the endpoint).
java.rmi.server.RMISocketFactory:
It implemets the RMIClientScocketFactory and RMIServerSocketFactory
interfaces. The static method setSocketFactory can be invoked to set the
socket factory by the application. But this could be done only once in
the application.
The transport invokes the createSocket and createServerSocket when
the RMI needs sockets to establish a communication.
When a remoteSocketFactory is specified while exporting the remote object, it will be downloaded at the time and used to create a custom client socket by calling the RMISocketFactory.createSocket() method.
java.rmi.server.RMIFailureHandler interface
Contains a method that determines whether a retry has to be made in
case a server socket creation was a failure. It must be registered first
with the RMISocketFactory by calling the setFailureHandler method. If not
registered a failurehandler, the default is to retry creation after a short
time.
Registry is a remote object that maps names to remote objects. Any server
can have its own registry or a single registry can serve a host.
The java.rmi.registry.Registry interface provides methods for looking
up, binding, unbinding, rebinding and listing the contents of a registry.
The java.rmi.Naming calls LocateRegistry.getRegistry method to get a remote
object that implement the Registry interface and call methods of that remote
object. java.rmi.Naming uses URL-based naming.
java.rmi.registry.Registry interface: Provides bind, unbind,
rebind, lookup and list methods.
java.rmi.registry.LocateRegistry class: provides getRegistry
and createRegistry methods.
getRegistry can be used to get a reference (stub) to a bootstrap
remote object registry on a host, including local host, on a particular
port or a default port.
createRegistry creates a local registry at a specified port.
java.rmi.server.RemoteStub: is the superclass of all remote stubs in RMI. Stubs are client side surrogates for remote objects and has exactly the same set of remote interfaces defined by the remote object's class; the stub class does not include the non-remote portions of the class hierarchy that constitutes the object's type graph.
Using wait, notify and notifyAll on the stub reference does not act on the actual remote object but on the client's local reference.
java.rmi.server.RemoteRef: all stubs contains a remoteRef which is a handle to the remote object. It is used to carry out remote calls to the remote object for which it is a reference.The remote call is carried out by using the new ivoke method of RemoteRef.
java.rmi.server.ServerRef: The server side handle for the remote object. It has methods to export remote object and also to get the current client using the remote object.
java.rmi.server.Skeleton: Deprecated in JDK1.2. It is
used by skeleton generated by rmic. It contains methods to dispatch a method
call to the actual remote object and also to get the list of all the operations/methods
found in the remote object.
java.rmi.dgc.DGC interface:
package java.rmi.dgc;
import java.rmi.server.ObjID;
public interface DGC extends java.rmi.Remote {
Lease dirty(ObjID[] ids, long sequenceNum,
Lease lease) throws java.rmi.RemoteException;
void clean(ObjID[] ids, long seqNum, VMID vmid,
boolean strong) throws java.rmi.RemoteException;
}
A client trying to get remote object references calls the dirty
method with the ids of the remote objects for which refrences are sought
of. The DGC puts the VMID of the client to reference list of the
remote object and returns a Lease object. The lease object contains the
VMID of the client that the DGC has used and a lease time which could be
different from what the client requested.
The client need to increase the lease by making more dirty calls, if
it wants the reference for more than the initial lease time. For dirty
calls that fails the correponding clean call must have the strong argument
as true, indicating the DGC that the sequence number must be remembered.
A client need to make only one dirty call even if it has multiple reference
to the same remote object.
When the client has finished using the reference it must call the call
method so that DGC can reduce the client VMID from the reference list.
java.rmi.dgc.Lease class:
package java.rmi.dgc;
public final class Lease implements java.io.Serializable {
public Lease(VMID id, long duration);
public VMID getVMID();
public long getValue();
}
The lease class stores the client VMID and the lease duration.
java.rmi.server.ObjID class
package java.rmi.server;
public final class ObjID implements java.io.Serializable {
public ObjID ();
public ObjID (int num);
public void write(ObjectOutput out) throws java.io.IOException;
public static ObjID read(ObjectInput in)
throws java.io.IOException;
public int hashCode()
public boolean equals(Object obj)
public String toString()
}
The ObjId represents a unique remote object in a virtual machine.Each identifier contains an object number and an address space identifier that is unique with respect to a specific host. An object identifier is assigned to a remote object when it is exported.
java.rmi.server.UID class:
The class UID is an abstraction for creating identifiers that are unique with respect to the host on which it is generated. A UID is contained in an ObjID as an address space identifier. A UID consists of a number that is unique on the host (an int), a time (a long), and a count (a short).
java.rmi.dgc.VMID:
The class VMID provides a universally unique identifier among all Java virtual machines. A VMID contains a UID and a host address. A VMID can be used to identify client virtual machines.
An active object is a remote object that is instantiated and exported in a Java VM on some system. A passive object is one that is not yet instantiated (or exported) in a VM, but which can be brought into an active state. Transforming a passive object into an active object is a process known as activation. Activation requires that an object be associated with a VM, which may entail loading the class for that object into a VM and the object restoring its persistent state (if any).
RMI uses Lazy Activation. In this, activation of remote object is deferred until the client's first use.
Lazy Activation: uses "faulting" remote references. A remote object's stub contains a "faulting" reference which contains both:
Components:
Faulting reference, Activator, Activation Group (one per JVM)
and the remote Object.
Activator: One per host, and does the following --
Activation Group is the entity per JVM that receives request to activate a remote object and returns the activated object to the activator.
The protocol:
A faulting reference uses an activation identifier and calls the activator
(an internal RMI interface) to activate the object associated with the
identifier. The activator looks up the object’s activation descriptor (registered
previously). The object’s descriptor contains:
An Activable Remote Object:
An activatable remote object must have:
ActivationID class : it denotes remote objects that can be activated
over time. It contains: remote reference to its activator and a unique
id for remote object. The activationId of a remote object is obtained by
registering the remote object to the activation system.
RMI provides an activation system implementation: rmid daemon.
It should be started before registering any activatable remote object.
Activatable class:
The simplest way to make a remote object activatable is to subclass
java.rmi.activation.Activatable class. The concrete subclass must call
the constructor of the Activatable class to automatically register itself
with the acticvation system and also to be exported.
An Example Activatable Remote Object:
package examples;
public interface Server extends java.rmi.Remote {
public void doImportantStuff()
throws java.rmi.RemoteException;
}
public class ServerImpl extends Activatable implements Server
{
// Constructor for initial construction, registration and export
public ServerImpl(String codebase, MarshalledObject data)
throws ActivationException, java.rmi.RemoteException
{
// register object with activation system, then
// export on anonymous port
super(codebase, data, false, 0);
}
// Constructor for activation and export; this constructor
// is called by the ActivationInstantiator.newInstance
// method during activation in order to construct the object.
public ServerImpl(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException
{
// call the superclass’s constructor in order to
// export the object to the RMI runtime.
super(id, 0);
// initialize object (using data, for example)
}
public void doImportantStuff() { ... }
}
If the Activatable remote object does not extend the Activatable class, then it must take the task of exporting and registering itself in both the initial constructor and the activation constructor:
package examples;
public class ServerImpl extends SomeClass implements Server
{
// constructor for initial creation
public ServerImpl(String codebase, MarshalledObject data)
throws ActivationException, java.rmi.RemoteException
{
// register and export the object
Activatable.exportObject(this, codebase, data, false, 0);
}
// constructor for activation
public ServerImpl(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException
{
// export the object
Activatable.exportObject(this, id, 0);
}
public void doImportantStuff() { ... }
}
Registering an Activation Descriptor without creating an object:
Server server;
ActivationDesc desc;
String codebase = “http://zaphod/codebase/”;
MarshalledObject data = new MarshalledObject(“some data”);
desc = new ActivationDesc(“examples.ServerImpl”, codebase, data);
server = (Server)Activatable.register(desc);
The remote stub returned by register method implements the same set of remote interfaces as the Server class and can be used where a Server reference is used.
The Activator Interface:
package java.rmi.activation;
public interface Activator extends java.rmi.Remote
{
java.rmi.MarshalledObject activate(ActivationID id,
boolean force)
throws UnknownObjectException, ActivationException,
java.rmi.RemoteException;
}
The activate method activates the object associated with the activation
identifier, id. If the activator knows the object to be active already
and the force parameter is false, the stub with a “live” reference is returned
immediately to the caller; otherwise, if the activator does not know that
corresponding the remote object is active or the force parameter is true,
the activator uses the activation descriptor information (previously registered
to obtain the id)to
determine the group (VM) in which the object should be activated. If
an ActivationInstantiator corresponding to the object’s group already exists,
the activator invokes the activation instantiator’s newInstance method
passing it the id and the object’s activation descriptor.
If the activation instantiator (group) for the object’s group descriptor does not yet exist, the activator starts a new incarnation of an ActivationInstantiator executing (by spawning a child process, for example). When the activator re-creates an ActivationInstantiator for a group, it must increment the group’s incarnation number. Note that the incarnation number is zero-based. The activation system uses incarnation numbers to detect late ActivationSystem.activeGroup and ActivationMonitor.inactiveGroup calls. The activation system discards calls with an earlier incarnation number than the current number for the group.
When the activator receives the activation group’s call back (via the ActivationSystem.activeGroup method) specifying the activation group’s reference and incarnation number, the activator can then invoke that activation instantiator’s newInstance method to forward each pending activation request to the activation instantiator and return the result (a marshalled remote object reference, a stub) to each caller. Note that the activator receives a MarshalledObject instead of a Remote object so that the activator does not need to load the code for that object, or participate in distributed garbage collection for that object. If the activator kept a strong reference to the remote object, the activator would then prevent the object from being garbage collected under the normal distributed garbage collection mechanism.
The ActivationSystem Interface:
The ActivationSystem provides a means for registering groups and activatable
objects to be activated within those groups. The ActivationSystem works
closely with both the Activator, which activates objects registered via
the ActivationSystem, and the ActivationMonitor, which obtains information
about active and inactive objects and inactive groups.
rmid -stop [-port] is used to stop the activation system [at the specified port].
The ActivationMonitor Class:
An ActivationMonitor is specific to an ActivationGroup and is obtained
when a group is reported via a call to ActivationSystem.activeGroup (this
is done internally by the ActivationGroup.createGroup method). An activation
group is responsible for informing its ActivationMonitor when either: its
objects become active, inactive or the group as a whole becomes inactive.
The ActivationInstantiator Class:
The ActivationInstantiator is responsible for creating instances of
activatable objects. A concrete subclass of ActivationGroup implements
the newInstance method to handle creating objects within the group.
The ActivationGroupDesc Class:
An activation group descriptor (ActivationGroupDesc) contains the information
necessary to create or re-create an activation group in which to activate
objects in the same Java VM.
Such a descriptor contains: