Java Remote Method Invocation (RMI)

Java RMI is the Java language's native mechanism for performing simple, yet powerful networking computations. RMI allows us to write distributed objects in Java, enabling objects to communicate; in memory, across Java Virtual Machines, and across physical devices. Applications that use RMI to invoke methods in these remote objects are typically composed of two separate programs: an RMI client that makes requests and an RMI server that executes the requests and returns results to the client.

Distributed Object Technologies

A remote procedure call (RPC) is a procedural invocation from a process on one machine to a process on another machine. RPCs enable traditional procedures to reside on multiple machines , yet still remain in communication. They are a simple way to perform cross-process or cross-machine networking.

A remote method invocation takes the RPC concept towards distributed object communications. The object is the center of the Java world. Distributed object technologies provide the infrastructure that lets us have two objects running on two different machines talk to one another using an object-oriented paradigm. Using traditional networking, we have to write IP socket code to let two objects on different machines talk to each other. While this approach is OK, it is prone to error. The ideal solution is to let the Java VM do the work. We call a method in an object, and the VM determines where the object is located. If it is a remote object, it will do all the dirty network stuff automatically.

Several technologies like CORBA enable developers to provide a clean, distributed programming architecture. CORBA has a very wide reach and is wrought with complexities associated with its grandiose goals. Unlike other programming languages, Java has distributed support built into its core. Borrowing heavily from CORBA, Java supports a simpler pure Java distributed object solution called Remote Method Invocation.

RMI is an API that lets use mostly ignore the fact that we have objects distributed all across a network. RMI allows us to invoke methods on objects remotely, not merely procedures. We can develop our networked code as objects as object-oriented software development is advantageous in many ways. But RMI is beset with numerous challenges due to the fact that the operating environment is heterogeneous. The primary hurdles are different data representations among systems, network or machine stability, security and garbage collection. The novel features available with Java at the programming level and the additional Java RMI features help to overcome these drawbacks tremendously and facilitate development of RMI applications a very simple and safe task.

RMI and Interface

In RMI, any object whose methods can be invoked from another Java VM is called a remote object. Remote objects are networked objects, which expose methods that can be invoked by remote clients. Remote objects and clients can be on the same address space or on the machines connected with the Internet.

In Java RMI, every object to be remotely accessed has to have an attached interface, which is a collection of methods that the objects can accomplish. The remote object class has implementations for all the methods exposed through the interface. The one distinct advantage of having interface is that it is possible to plug into the class different, robust and efficient implementations for methods without enacting any change to the exposed interface.

Every remote object also has to implement the interface java.rmi.Remote. This can be done by creating our custom interface which extends java.rmi.Remote. The custom interface should have within it a copy of each method our remote exposes to the perspective clients. An additional restriction of RMI is that each method must also throw a java.rmi.RemoteException apart from other custom exceptions. A java.rmi.RemoteException is thrown when there is a problem with the network, such as a machine crashing or the network dying.

Stubs and Skeletons

Java RMI facilitates transparent networking. We can invoke methods on remote objects just we do for local objects. RMI completely masks whether the object being invoked is local or remote. This is called local/remote transparency. But accomplishing this transparency is not that much easy. If the object is remote, then RMI needs a local proxy that we can invoke on. This local proxy object is called a stub and it is responsible for accepting method calls locally and delegating those invocations to their actual implementations in the remote object, which is located across the network.

In JDK 1.3, RMI requires that clients use a stub to connect to a remote VM. Today we generate stub classes with the rmic command line tool. However future versions of RMI might allow clients to build stubs dynamically at run time using dynamic proxies. Until that feature is added, clients need a way to guarantee that stubs are available. Installing the stubs on the client is not always viable, because stubs are an implementation detail that might change over time. Dynamic class loading allows client virtual machines to find stubs at runtime, without any special coding in the client.

Stubs actually do a lot of activities behind the scenes. That every method including both in-coming and out-going parameters has to be serialized to be sent over the network is being performed by stub. This process is called marshalling.

Similarly, the remote objects to be kept of these networking issues, a skeleton object is there on the server side. Skeletons accepts invocations from stubs over the network and delegates the calls to the real object. Also skeleton has to unmarshal the marshalled parameters before contacting the real object. Thus stubs and skeletons appear to be complementary in their assigned tasks.

Java RMI Registry

For a client to communicate with a server, the client has to find the location of remote objects. Towards this, each and every object to be accessed remotely has to register with RMI Registry with an unique name. Thus if there is any request for a particular object, the registry will direct the request to the correct object. RMI Registry is being implemented as a giant hash-table that maps names to objects. The RMI Registry listens to a well-known port. Thus whenever there is a request call from any client, the Registry reads the request, looks up the hash table, gets the name of the remote object requested, and returns the stub for the remote object to the client. Then, as told above, clients can talk with the stub directly in its local environment.

There can be more than one RMI Registry on a machine but only one Registry per Java Virtual Machine. Each Registry can be bound to a specific port. If a particular port is not assigned, Registry will listen at port 1099. RMI Registry can be started in two ways. We can launch it as a stand-alone program or can be activated by our Java program by accessing the java.rmi.Registry class. Thus an RMI Registry is a remote object used by clients for initially connecting to know the exact location of remote object.

RMI Uniform Resource Locator (URL)

An RMI URL is a Java String object that is being used to locate a remote object on another Java VM. An example RMI URL looks like "rmi:// Here is the server machine and myObject is the remote object for which the client needs the object reference. Thus on knowing this address, any one from any part of the world can connect and gets things done. An remote object server binds an object to a URL by calling the static rebind() method of java.rmi.Naming.

RMI URLs are good for its purpose and fairly inflexible for enterprise distributed computing. The reason behind this is that one has to hard-code RMI URLs into his Java code. If suppose the remote object location gets changed, then each client accessing this remote object has to undergo the relevant changes in its code to reflect the object location change. To make this process of locating distributed objects in the network flexible, Sun Microsystems has come out with Java Naming and Directory Interface (JNDI). JNDI does away with hard-coding of object location in the code and takes away the necessity of making changes in the code if there is any change in the location. As JNDI came out later, it could not be attached with Java RMI architecture.

Looking up a Remote Object

Thus we have an RMI Registry for storing the names of the remote objects and RMI URL for identifying the locations of objects over the network. For clients to acquire a reference and to connect to a remote object, he has to use java.rmi.Naming class, which is responsible for connecting to RMI Registries for the purposes of finding remote objects. The most important method that the class java.rmi.Naming exposes is the lookup(), which takes an RMI URL, connects to an RMI Registry on a target Java VM and returns a java.lang.Object representing the remote object. The returned Object is actually the remote stub, and it can be cast into our remote object's interface type.

RMI Compiler

The stubs and skeletons discussed above provide a screen to block networking issues and make it appear as if things are going on locally. Both stubs and skeleton must implement our custom remote object's interface and they must be generated anew for each remote object class that we create. Sun Microsystems ships a generating tool called as rmic, the RMI Compiler, to perform this task. It takes as input our remote object implementation, that is, the class that implements our remote interface, and creates a stub and skeleton for that remote object. The stub mimics each method exposed by the remote object, copying the method signatures exactly, so clients can call that stub locally as if the remote object itself were there.

Parameter Passing Techniques

When invoking a method using RMI, all parameters to the remote method are passed by value. This means that all parameters are copied from one machine to the other when the target method is invoked. There is a big problem with passing by value. When we pass an object over the network and that object contains references to other objects, how are those references resolved on the target machine. A memory address on one machine does not map to the same memory address on another machine. Also the referenced object may not even exist on the target machine. To overcome this issue, Java introduces the concept of object serialization. Serialization is the conversion of a Java object into a bit-blob representation of that object. Once in bit-blob form, the object can be sent anywhere - onto our hard disk or across the network. When we are ready to use the object again, we must deserialize the bit-blob back into a Java object.

When we pass an argument using RMI, RMI checks whether our object is serializable. If it is, it serializes the object and sends it over the network. The recipient takes the object and deserializes it, making it available for use.

Passing parameters by value has its own inefficiencies. What if our referenced graph of objects is very large?. What if we have lots of state to send across the network?. The ensuing network lag from performing the invocation may be unacceptable. To set things right, RMI simulates a pass-by-reference convention, which means the arguments are not copied over. Rather, the remote method works with the original object. If we want a parameter by reference, the parameter itself must be a remote object, that is, an object that is invokable remotely. Let us consider the scenario where a remote host acquires a reference to this remote object, perhaps as a parameter to one of its methods. That remote host can then invoke operations on this remote object, the operations will occur on the local host, rather than on the remote host. That is, the operation is itself a remote method invocation that runs over the network.

What exactly is passed as a parameter when passing a remote object by reference is that remote object's stub object. Stubs are network-aware references to remote objects. Just as in regular Java we have references to objects, in RMI we have remote references to objects. java.rmi.RemoteStub objects are the manifestation of those remote references. These are the exact same stubs described earlier that are generated by the rmic tool.

Java RMI simulates pass by reference only if the object parameter being passed is itself a remote object. In this case, the stub for the remote object is passed, rather than the object itself being serialized. Because Java RMI stubs are serializable, they are passable over the network as a bit-blob. Thus practically all parameters in Java RMI are passed by value only. Thus, Java RMI only simulates pass by reference by passing a serializable stub rather than serializing the full original object. In summary, firstly all Java basic primitive data types are passed by value when calling methods remotely and hence any changes to the data on the remote host are not reflected in the original data. Secondly, if we want to pass an object over the network by value, it must implement java.lang.Serializable and as before, changes if any will not be reflected in the original data. Finally, if we want to pass an object over the network by reference, it must be a remote object and it must implement java.rmi.Remote. A stub for the remote object will be serialized and passed to the remote host. The remote host can then use that stub to invoke call-backs on our remote object. There is only one copy of the data at any time, which means that all hosts are updating the same data.

Exporting Remote Objects

To make our object a remote object available to be invoked on by remote hosts, our remote object class has to perform one of the following steps: Extend the class java.rmi.server.UnicastRemoteObject. UnicastRemoteObject is a subclass of RemoteObject class that hides most of the details of making an object available to remote clients. When our remote object is constructed, it will automatically call the UnicastRemoteObject's constructor, which will make the object available to be called remotely.

Suppose our objects are inheriting implementation from another class, we can not extend UnicastRemoteObject as Java does not support multiple inheritance. In this case, we must manually export our object so that it is available to be invoked on by remote hosts. To export an object, call java.rmi.server.UnicastRemoteObject.exportObject().

RMI Remote Exceptions

All methods that can be called remotely and all constructors for remote objects must throw a special exception called java.rmi.RemoteException. The methods we write will never explicitly throw this exception. Instead, the local virtual machine will throw it when we encounter a network error during a remote method call. Remote Exceptions are useful for detecting nasty networking issues, such as application death, machine crashes, and network failures.

A RemoteException is unlike any other exception. When we write an application to be run on a single virtual machine, we know that if our code is solid, we can predict potential exception situations and where they might occur. We can count on no such thing with a RemoteException. It can happen at any time during the course of a remote method call, and we may have no way of knowing why it happened. we therefore need to write application code with the knowledge that at any point of our code can fail for no discernible reason and have contingencies to support such failures.

This makes it difficult to separate our business logic code from our networking code because all those annoying RemoteExceptions being thrown, which we need to wrap up by using try ... catch blocks. How we can separate the two effectively?

One common design pattern with RMI is to construct a gateway remote object that is dependent on RMI. Once a client has acquired a handle to the gateway remote object, the gateway can serve as a conduit into an entire suite of objects running within a JVM. All method invocations can happen through the gateway. When the gateway receives a request, it delegates calls to other local objects on its machine - those objects providing the business logic. In the simplest case, only one reference to cone remote object on another machine is needed in order to access that machine's functionality. This technique also reduces the number of times one need to form that initial RMI connection.

RMI and Firewalls

A firewall is a barrier that controls network traffic for the purposes of security. Although necessary for a secure intranet, a firewall can be a real hassle to deal with when we start talking about distributed computing. Many corporate intranets will let users through the firewall only to browse the web, which happens at port 80 and this port is reserved for Web traffic. Thus it would seem that firewalls make it impossible to do any form of client/server communication in other ports. This is very cruel. If we code a Java applet to be downloaded by clients who are sitting behind their corporate intranet firewalls and if our applet needs any communication with our central server, we are out of luck. The only way to overcome this restriction is somehow to make use of port 80. That is, we have to send all client/server traffic as regular http requests/responses. This is what RMI does when facing firewalls. RMI passes through firewalls by simulating an RMI Registry listening at port 80. It wraps all transported data in Web tags, so that they look like normal Web communications. The masking of a request as an http request on port 80 is called http tunneling through firewalls. By tunneling through and pretending that we are doing normal http communications, we can reach our target RMI Registry.

To use this tunneling, a Web server must be deployed on the same machine on which the target RMI Registry sits. That Web server acts as a router - it takes requests that come in at port 80 (which the firewall permits) and routes them to the actual port where the RMI Registry listens on that machine. This is being accomplished by a CGI script included with the Sun's Java 2 platform. By this mechanism, clients can get through their firewalls because their firewalls think they are making normal http port 80 communications. RMI does this task transparently.

RMI with other Java APIs

RMI combines well with other related technologies and enhances well to perform enterprise-level services by coexisting with other Java APIs, such as Java Naming and Directory Interface, Java Transaction Service and Java Transaction API. We can achieve some semblance of cross-language interoperability by combining RMI with the Java Native Interface (JNI). We can also perform relational database operations over the network by combining Java Database Connectivity (JDBC) with RMI. RMI also plays a very critical role in Enterprise JavaBeans architecture.

Java RMI is a lightweight networking technology proposed by Sun Microsystems. RMI is simple, easy to learn, and to apply for real-world problems. RMI is Java-centric. As Java is platform-independent, RMI is portable across many platforms. RMI takes care many of the networking tasks and facilitates connections and communications. Java RMI has come out with many valuable classes for designing and deploying Java objects for remote invocations. Thus Java RMI adds distributed object computing power to Java applications.

Click for RMI - Links

Click for Distributed Object Computing Page

Back to my Home Page