オブジェクトを一つのアドレス空間から他のアドレス空間へユーザから見て透過的な方法で伝達するために、オブジェクト直列化(Java 言語に特有の設計です)という技術が使われます。 オブジェクト直列化についてはこの章ではプリミティブとオブジェクトの整列化に関連した部分のみを説明します。更に完全な内容についてはオブジェクト直列化仕様を参照してください。
ダイナミックスタブローディングという別な技術により、クライアント側のスタブがリモートオブジェクト自体と同じリモートインタフェースを実装することが可能になります。 この技術は、クライアント側で厳密な型のスタブがまだ利用できないときに、クライアントが型キャストと型チェックのために Java 言語の組み込み演算子を使えるようにします。

リモートサーバオブジェクト上のメソッドを呼び出すクライアントは実際にはリモートオブジェクトのスタブまたはプロキシをリモートオブジェクトへの通路として利用します。クライアント側が持っているリモートオブジェクトへの参照はローカルスタブへの参照です。 このスタブはリモートオブジェクトのリモートインタフェースの実装であり、リモート参照層を経由して、サーバオブジェクトへ呼び出し要求を転送します。 スタブは rmic コンパイラにより生成されます。
リモート参照層 は呼び出しの意味解析を司ります。例えば、サーバが単一のオブジェクトなのか、あるいは複製オブジェクトで複数のロケーションとの通信が必要なのかを決定するのはリモート参照層の仕事になります。 リモートオブジェクトの実装はそれぞれが自分のリモート参照セマンティクスを選択します −サーバが単一オブジェクトなのか、あるいは自分自身の複製との通信を必要とする複製オブジェクトなのかを選択します。
サーバへの参照のセマンティクスもリモート参照層が取り扱います。 例えば、リモート参照層はオブジェクト参照方法の違いを抽出して、(a) そのサーバはあるマシン上で常時実行されているのか、(b) そのサーバはメソッド呼び出しが起こったときだけ活性化されて実行する実装になっているのかを判定します。 リモート参照層の上に位置する層からはこれらの差を認識することができません。
トランスポート層 は接続のセットアップ、接続管理、そしてトランスポートのアドレス空間にあるリモートオブジェクト(リモート呼び出しのターゲット)のトラッキングとディスパッチを司ります
リモートオブジェクトへのディスパッチのために、トランスポートはリモート呼び出しをその上にあるリモート参照層に伝えます。リモート参照層は要求をサーバ側スケルトンに渡す前にサーバ側が必要とする動作をすべて取り扱います。 リモートオブジェクトのスケルトンは、実際のメソッド呼出しを実行するリモートオブジェクトの実装への呼び出しを作成します。
呼び出しに対する返り値はスケルトンを通ってリモート参照層へ伝えられ、サーバ側のトランスポートへ伝えられ、次にトランスポートを通過して上に進みリモート参照層を通ってクライアント側のスタブに到達します。
リモートオブジェクトへのスタブとはリモートオブジェクトへのクライアント側のプロキシです。 このようなスタブはリモートオブジェクトの実装がサポートするすべてのインタフェースを備えています。 クライアント側のスタブは次の役目を果たします。
それぞれのリモートオブジェクトの実装は自分用のリモート参照サブクラスを選択します。この層では、次に示す例のように種々の呼び出しプロトコルが実行可能です。
同じような方法で、サーバ側コンポーネントは、スケルトンにリモートメソッド呼び出しを伝達するのに先立ち、特定のリモート参照セマンティクスを実装します。このコンポーネントは例えば、複製グループの他のサーバと交信しながら強力なマルチキャストデリバリーを取り扱います(マルチキャストデリバリは JDK 1.1 リリースの RMI には含まれないことに注意)。
リモート参照層はストリーム指向接続の abstraction を介してトランスポート層への伝達を行います。接続実装の詳細はトランスポートが取り扱います。 接続はストリームをベースとするインタフェースを表しますが、その抽象化の下では接続のないトランスポートを実装することも可能です。
RMI システムのトランスポートは4つの基本的な abstraction から構成されます。
参照カウント式ガベージコレクションを実行するために、RMI のランタイムは各 Java 仮想マシン内で生き残っている参照を常に監視しています。 生きた参照が Java 仮想マシンに入ってくると参照カウントは増加します。あるオブジェクトへの最初の参照は「参照が起こった」というメッセージをそのオブジェクトのサーバへ送ります。生きた参照がローカルな仮想マシン内で参照されていないことが分かると、ファイナライズされてカウントは減少します。最後の参照が廃棄されると、「参照されていない」というメッセージがサーバに送られます。このプロトコルには微妙な点が数多くありますが、それらは大部分参照と非参照メッセージの順序を正しく管理してオブジェクトが不必要に回収されるのを防ぐことを保証します。
リモートオブジェクトがどのクライアントからも参照されていない状態になると、RMIランタイムは、そのオブジェクトを弱い参照(weak reference)によって参照します。 弱い参照になると Java 仮想マシンのガベージコレクタは、もしそのオブジェクトへのローカル参照が他に存在しなければ、オブジェクトは廃棄します。分散ガベージコレクションアルゴリズムは、オブジェクトへのノーマルな参照と弱い参照を保持しながら、ローカル Java 仮想マシンのガベージコレクタと相互作用を続けます。 通常のオブジェクトのライブサイクルと同様に、ガベージコレクタがそのオブジェクトへの参照はもはや存在しないと判断すると finalize が呼び出されます。
リモートオブジェクトへのローカルな参照が存在している間はガベージコレクトされることはなく、オブジェクトはリモート呼び出しに渡されたり、クライアントに返されたりします。リモートオブジェクトを渡すと、それが渡された仮想マシンの識別子が参照セットへ追加書き込みされます。
非参照の通知を必要とするリモートオブジェクトは java.rmi.server.Unreferenced インタフェースを実装しなければなりません。これらの参照が存在しなくなると unreferenced メソッドが呼び出されます。
unreferenced は参照セットが空であると呼び出されますから、このメソッドは複数回呼び出されることがあります。リモートオブジェクトが回収されるのは、リモートとローカルを含めて参照が全く無くなった場合だけです。
もし、クライアントとリモートサーバの間にネットワークパーティションが存在すると、リモートオブジェクトのコレクションが早すぎる時期に起こり得ます (トランスポートがクライアントがクラッシュしたと判断するかもしれないからです)。
時期尚早な回収の可能性があるため、リモート参照は参照に関する完全な整合性を保証するものではありません。別な言葉で言えば、リモート参照が実際に存在するオブジェクトを参照していない可能性が常に存在するということです。対象を持たない参照を使おうとすると RemoteException が発生し、その取り扱いはアプリケーションが行わなければなりません。
RMI はこのテクニックを一般化し、動的クラスローディングと呼ばれるメカニズムによりリモートオブジェクトのメソッド呼び出しに必要なクラスを(Java言語のアーキテクチャでいうところのニュートラルなバイトコードとして)ランタイムにローディングします。 これらのクラスは:
rmic コンパイラにより作成する。)
main メソッドが java コマンドで実行される)はデフォルトのクラスローダによりローカル CLASSPATH で指定される場所からロードされる。このクラスが直接使用するすべてのクラスも順次デフォルトクラスローダによりローカル CLASSPATH で指定される場所からロードされる。
java.rmi.server.codebase プロパティのURLが使用される。
java.rmi.server.codebase URL を使用する。
アプリケーションは java.rmi.server.useCodebaseOnlyプロパティによって構成することができ、ネットワークホストからのクラスローディングを禁止して, ローカルに定義された codebase からのみロードさせることができます。
必要なクラスがロードできなければ、メソッド呼び出しは例外を発生して失敗します。
RMISecurityManager のインスタンスを作成するか、ユーザ定義のセキュリティマネージャを作成する。
RMIClassLoader.loadClass メソッドを使用する。コード中にクラスの名前を陽に記述することが出来ないが、文字列またはコマンドライン引数として与えなければならない。そうしないとデフォルトローダがクラスファイルをローカル CLASSPATH からロードしようとする。
newInstance メソッドを使ってクライアントのインスタンスを作成し、それを Runnable にキャストする。クライアントは java.lang.Runnable インタフェースを実装しなければならない。Runnable インタフェースは実行スレッドをスタートさせるよく定義されたインタフェースを提供する。
run メソッド (Runnable インタフェースに含まれる)を呼び出ししてクライアントをスタートさせる。
import java.rmi.RMISecurityManager;
import java.rmi.server.RMIClassLoader;
public class LoadClient
{
public static void main()
{
System.setSecurityManager(new RMISecurityManager());
try {
Class cl = RMIClassLoader.loadClass("myclient");
Runnable client = (Runnable)cl.newInstance();
client.run();
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
}
}
}
loadClass メソッドが URL によってクラスをロードするようにしなければなりません。
例えば:
java -Djava.rmi.server.codebase=http://host/rmiclasses/ LoadClient
Class cl = RMIClassLoader.loadClass(url, "myclient");
このブートストラッピングテクニックを使わない場合は、クライアントのコードで直接参照されるクラスはクライアントの CLASSPATH の下になければなりません。しかも RMIClassLoader を使ってネットからダウンロードできる Java クラスはクライアントプログラムからは直接参照できないのです。これらのクラスにはスタブ、スケルトンそしてリモートメソッド呼び出しのパラメーター、返り値として使われる拡張クラスがあります。
セキュリティマネージャは Java プログラムの最初のアクションとして起動され、それ以後の動作を管理できなければなりません。セキュリティマネージャはロードされたクラスが Java の標準的な安全規約に従っていることを保証します。クラスが信頼できるソースからロードされたか(例えばアプレットホスト)とか危険性のある機能にアクセスしなかったか等のチェックを行います。セキュリティマネージャが課す制限についての完全な解説は AppletSecurity クラスや RMISecurityManager RMISecurityManager クラスのドキュメントで読むことができます。
アプレットは常に AppletSecurity クラスの課す制限下にあります。セキュリティマネージャはクラスがアプレットホストかまたは信頼のおける codebase ホストのみからロードされるのを保証します。このため、アプレット開発者はアプレットホスト上に必要なクラスをインストールしなければなりません。
アプリケーションは自分のセキュリティマネージャを定義するか、または強い制限を課する RMISecurityManager を使うことになります。セキュリティマネージャが置かれていない場合は、アプリケーションはネットワークからクラスをロードすることができません。
クライアントやサーバプログラムは通常、ローカルシステムからロードされたクラスによって実装されますからセキュリティマネージャからの制限を受けません。しかし、クライアントプログラム自体がクライアントのブートストラッピングに解説されている方法でネットワークからダウンロードされた場合には、クライアントはセキュリティマネージャからの制限を受けることになります。
あるクラスが RMIClassLoader によってロードされると、そのクラスがそれ以後直接使用するクラスも RMIClassLoader によってロードされ、やはりセキュリティマネージャの管理を受けます。
セキュリティマネージャが置かれていても、java.rmi.server.useCodebaseOnly プロパティを true に設定するとクラスをストリームに埋め込んだ URL から直列化されたオブジェクトによってダウンロードすることが出来なくなります(この場合でもローカルに定義された java.rmi.server.codebase からロードすることは可能です)。
java.rmi.server.useCodebaseOnly プロパティはクライアントとサーバ両方で指定できますが、アプレットに対しては適用できません。
もし、アプリケーションが自分のセキュリティマネージャを定義し、それがクラスローダの作成を禁止した場合は、クラスはデフォルトの Class.forName メカニズムによってロードされます。
したがって、サーバは自身のポリシーをセキュリティマネージャとクラスローダを介して定義することができ、RMI システムはポリシーにしたがった動作をします。
セキュリティマネジャーはすべて java.lang.SecurityManager abstract クラスから拡張されますが、このクラスはリソースの消費に関しては制約を課しません。したがって、現在の RMISecurityManager はロードされたクラスがリソースを間違って使用することについての予防策を持っていません。新しいセキュリティメカニズムが開発されましたので、RMI はこれを使うことになるでしょう。
よりオープンなサーバシステムでは、サーバがエクスポートするリモートオブジェクトのクラスがクライアントによりロードされ、クライアントが提供するリモートオブジェクトが必要とする場合だけにクラスをロードするように java.rmi.server.codebase を定義します。
サーバはセキュリティマネージャと RMI クラスローダの両方を持ち、これがサーバを保護します。さらに慎重にコンフィギュレーションしたい場合は、サーバは java.rmi.server.useCodebaseOnly プロパティを使って、クライアントが提供する URL からはクラスをロードできないようにします。
例えば、通常のアプレットシナリオは HTML ページ、アプレットコード、RMI サービス、そしてブートストラップレジストリを提供する HTTP サーバのために単一のホストを使用します。 このシナリオでは、すべてのスタブ、スケルトン、そしてサポートクラスは HTTP サービスを使ってロードされます。 RMI サービスが提供してアプレットに渡される(そしてサーバに返されるかもしれない)すべてのリモートオブジェクトは RMI サービスがすでに知っているクラスのためのものです。 このケースでは、ネットワークからのクラスのローディングが起こりませんから RMI サービスの安全性は非常に高くなります。
アプリケーションがローカルにロードされるときは、そのプログラムが直接使用するクラスもローカルに利用可能でなければなりません。 このシナリオではネットワーク上のソースからダウンロードされるのは、リモートインタフェースのクラス、スタブクラス、そしてリモートメソッド呼び出しで渡されるまたは返り値となる拡張クラスだけです。
アプリケーションがローカルディレクトリからロードされずに、クライアントのブートストラッピングで説明されるメカニズムを使ってネットワークソースからロードされる場合には、アプリケーションが使用するすべてのクラスはやはり同じネットワークソースからダウンロードすることができます。
ネットワークソースからのダウンロードを可能にするためには、各リモートオブジェクトサーバは、アプリケーションクラスと生成されたスタブ/スケルトンの所在を指定する java.rmi.server.codebaseプロパティを使ってコンフィギュレーションされなければなりません。
codebase プロパティが指定されると、RMI システムはクラスの URL を直列化されたクラスの形式で埋め込みます。
たとえ直列化されたオブジェクトのクラスがクラスをダウンロードできる URL で指定されたとしても、クラスやピアはクラスがローカルに利用可能である限りローカルにロードしようとします。
java.rmi.server.RMISocketFactory クラスを拡張して、クライアントとサーバソケットへのリソースプロバイダとなるソケットファクトリのデフォルト実装を提供しようとします。
このデフォルトソケットファクトリは、次に説明する方法でファイアウオールを透明に通り抜けるメカニズムを備えたソケットを作り出します。
java.rmi.server.RMISocketFactory.createSocket メソッドが提供します。
このようなデフォルト動作をするサーバ側のソケットはファクトリの java.rmi.server.RMISocketFactory.createServerSocket メソッドが提供します。
しかし、クライアントは java.rmi.server.disableHttp プロパティのブール値を true に設定して RMI 呼び出しを HTTP リクエストの形式にパッケージするのを禁止させることができます。
java.rmi.server.hostname プロパティとして指定されていなければなりません。
例えば、次のコマンドでマシン chatsubo.javasoft.com 上の RMI サーバクラス
ServerImpl をスタートさせることができます。
/cgi-bin/java-rmi に置きます。このスクリプトは、
ファイアウオールを通す場合、HTTP リクエストは一方向でのみ開始できるため、クライアントは自分のリモートオブジェクトをファイアウオールの外へエクスポートすることはできません。これはファイアウオールの外にあるホストが内側にあるクライアントのメソッド呼び出しを開始できないためです。