Java RMI
Java RMI contains a wealth of experience in designing and
implementing Java's Remote Method Invocation. If you're a novice
reader, you will quickly be brought up to speed on why RMI is such a
powerful yet easy to use tool for distributed programming, while
experts can gain valuable experience for constructing their own
enterprise and distributed systems.
With Java RMI, you'll learn tips and tricks for making
your RMI code excel. The book also provides strategies for working
with serialization, threading, the RMI registry, sockets and socket
factories, activation, dynamic class downloading, HTTP tunneling,
distributed garbage collection, JNDI, and CORBA. In short, a
treasure trove of valuable RMI knowledge packed into one book.
Chapter 10 Serialization
Serialization is the process of
converting a set of object instances that contain references to each other
into a linear stream of bytes, which can then be sent through a socket, stored
to a file, or simply manipulated as a stream of data. Serialization is the
mechanism used by RMI to pass objects between JVMs, either as arguments in a
method invocation from a client to a server or as return values from a method
invocation. In the first section of this book, I referred to this process
several times but delayed a detailed discussion until now. In this chapter, we
drill down on the serialization mechanism; by the end of it, you will
understand exactly how serialization works and how to use it efficiently
within your applications.
The Need for Serialization
Envision the banking application while a client is executing a
withdrawal. The part of the application we're looking at has the runtime
structure shown in Figure
10-1.
Figure 10-1. Runtime structure when making a withdrawal
|
|
What does it mean for the client to pass an instance of Money to the server? At a minimum, it means that the
server is able to call public methods on the instance of Money. One way to do this would be to implicitly make
Money into a server as well.[1]
For example, imagine that the client sends the following two pieces of
information whenever it passes an instance as an argument:
- The type of the instance; in this case,
Money.
- A unique identifier for the object (i.e., a logical
reference). For example, the address of the instance in memory.
The RMI runtime layer in the server can use this information to
construct a stub for the instance of Money, so that
whenever the Account server calls a method on what
it thinks of as the instance of Money, the method
call is relayed over the wire, as shown in Figure
10-2.
Figure 10-2. Relaying a Money method call from the server
|
|
Attempting to do things this way has three significant
drawbacks:
- You can't access fields on the objects that have been passed as
arguments.
Stubs work by implementing an interface. They implement the methods in
the interface by simply relaying the method invocation across the network.
That is, the stub methods take all their arguments and simply marshall them
for transport across the wire. Accessing a public field is really just
dereferencing a pointer--there is no method invocation and hence, there
isn't a method call to forward over the wire.
- It can result in unacceptable performance due to network latency.
Even in our simple case, the instance of Account is going to need to call getCents( ) on the instance of Money. This means that a simple call to makeDeposit( ) really involves at least two distinct
networked method calls: makeDeposit( ) from the
client and getCents( ) from the server.
- It makes the application much more vulnerable to partial failure.
Let's say that the server is busy and doesn't get around to handling the
request for 30 seconds. If the client crashes in the interim, or if the
network goes down, the server cannot process the request at all. Until all
data has been requested and sent, the application is particularly vulnerable
to partial failures.
This last point is an interesting one. Any time you have an
application that requires a long-lasting and durable connection between client
and server, you build in a point of failure. The longer the connection needs
to last, or the higher the communication bandwidth the connection requires,
the more likely the application is to occasionally break down.
TIP: The original design of the Web, with its
stateless connections, serves as a good example of a distributed application
that can tolerate almost any transient network failure.
These three reasons imply that what is really needed is a way to
copy objects and send them over the wire. That is, instead of turning
arguments into implicit servers, arguments need to be completely copied so
that no further network calls are needed to complete the remote method
invocation. Put another way, we want the result of makeWithdrawal( ) to involve creating a copy of the
instance of Money on the server side. The runtime
structure should resemble Figure
10-3.
Figure 10-3. Making a remote method call can create deep copies of the
arguments and return values
|
|
The desire to avoid unnecessary network dependencies has two
significant consequences:
- Once an object is duplicated, the two objects are completely
independent of each other.
Any attempt to keep the copy and the original in sync would involve
propagating changes over the network, entirely defeating the reason for
making the copy in the first place.
- The copying mechanism must create deep copies.
If the instance of Money references another
instance, then copies must be made of both instances. Otherwise, when a
method is called on the second object, the call must be relayed across the
wire. Moreover, all the copies must be made immediately--we can't wait until
the second object is accessed to make the copy because the original might
change in the meantime.
These two consequences have a very important third
consequence:
- If an object is sent twice, in separate method calls, two copies of
the object will be created.
In addition to arguments to method calls, this holds for objects that are
referenced by the arguments. If you pass object A, which has a reference to
object C, and in another call you pass object B, which also has a reference
to C, you will end up with two distinct copies of C on the receiving side.
Drilling Down on Object Creation
To see why this last point holds, consider a client that
executes a withdrawal and then tries to cancel the transaction by making a
deposit for the same amount of money. That is, the following lines of code are
executed: server.makeWithdrawal(amount);
....
server.makeDeposit(amount);
The client has no way of knowing whether the server still has a
copy of amount. After all, the server may have used
it and then thrown the copy away once it was done. This means that the client
has to marshall amount and send it over the wire to
the server.
The RMI runtime can demarshall amount, which is the instance of Money the client sent. However, even if it has the
previous object, it has no way (unless equals( )
has been overridden) to tell whether the instance it just demarshalled is
equal to the previous object.
More generally, if the object being copied isn't immutable, then
the server might change it. In this case, even if the two objects are
currently equal, the RMI runtime has no way to tell if the two copies will
always be equal and can potentially be replaced by a single copy. To see why,
consider our Printer example again. At the end of
Chapter 3, we considered a list of possible feature requests that could be
made. One of them was the following:
Managers will want to track resource consumption.
This will involve logging print requests and, quite possibly, building a set
of queries that can be run against the printer's log.
This can be implemented by adding a few more fields to DocumentDescription and having the server store an
indexed log of all the DocumentDescription objects
it has received. For example, we may add the following fields to DocumentDescription: public Time whenPrinted;
public Person sender;
public boolean printSucceeded;
Now consider what happens when the user actually wants to print
two copies of the same document. The client application could call: server.printDocument(document);
twice with the "same" instance of DocumentDescription. And it would be an error for the RMI
runtime to create only one instance of DocumentDescription on the server side. Even though the
"same" object is passed into the server twice, it is passed as parts of
distinct requests and therefore as different objects.
TIP: This is true even if the runtime can
tell that the two instances of DocumentDescription are equal when it finishes
demarshalling. An implementation of a printer may well have a notion of a
job queue that holds instances of DocumentDescription. So our client makes the first
call, and the copy of document is placed in the
queue (say, at number 5), but not edited because the document hasn't been
printed yet. Then our client makes the second call. At this point, the two
copies of document are equal. However, we don't
want to place the same object in the printer queue twice. We want to place
distinct copies in the printer queue.
Thus, we come to the following conclusion: network latency, and
the desire to avoid vulnerability to partial failures, force us to have a deep
copy mechanism for most arguments to a remote method invocation. This copying
mechanism has to make deep copies, and it cannot perform any validation to
eliminate "extra" copies across methods.
TIP: While this discussion provides examples
of implementation decisions that force two copies to occur, it's important
to note that, even without such examples, clients should be written as if
the servers make independent copies. That is, clients are written to use
interfaces. They should not, and cannot, make assumptions about server-side
implementations of the interfaces.
1. Just to be clear: doing things
this way would be a bad idea (and this is not the way RMI passes instances
over the wire).
New on the Java Boutique:
New Review:
Time Management Made Easy with the Quartz Enterprise Job Scheduler
Why not just use the Java timer API? This open source scheduling
API boasts simplicity, ease-of-integration, a well-rounded feature
set, and it's free!
New Applet:
Reverse Complement
Reverse Complement is a simple applet that converts DNA or RNA
sequences into three useful formats.
Elsewhere on internet.com:
WebDeveloper Java
Lots of Java information on webdeveloper.com
WDVL Java
Thorough Java resource at the Web Developer's Virtual Library.
ScriptSearch Java
Hundreds of free Java code files to download.
jGuru: Your View of the Java Universe
Customizable portal with online training, FAQs, regular news updates, and tutorials.
|