非常に単純なRMIサーバーを作成していて、断続的にJava.rmi.NoSuchObjectExceptions
単体テスト。
同じオブジェクトに対してリモートメソッド呼び出しの文字列があり、最初のいくつかは通過しますが、後の呼び出しは失敗することがあります。その間のサーバーオブジェクトの登録を解除するために何もしていません。
これらのエラーは常に表示されるとは限りません。ブレークポイントを設定すると、表示されない傾向があります。それらのハイゼンバグは、デバッガーの実行速度を低下させてそれらを見ると競合状態が解消されますか?テストまたはサーバーコードでマルチスレッドが実行されていません(ただし、RMIスタック内にある可能性はありますか?)。
私はこれをEclipseのJUnitプラグインを介してMac OS X 10.5(Java 1.5)で実行しており、RMIサーバーとクライアントの両方が同じJVMにあります。
これらの例外の原因は何ですか?
_Java.rmi.Remote
_インターフェースを実装するオブジェクトへの強い参照を維持し、それが reachable のままになるようにします。つまり、ガベージコレクションの対象外となります。
以下は _Java.rmi.NoSuchObjectException
_ を示す短いプログラムです。スクリプトは自己完結型であり、RMIレジストリを作成し、単一のJVMに「クライアント」と「サーバー」を作成します。
このコードをコピーして、_RMITest.Java
_という名前のファイルに保存するだけです。選択したコマンドライン引数を使用してコンパイルし、呼び出します。
-gc
_(デフォルト)サーバーの起動後、クライアントがサーバーに接続する前に、ガベージコレクターを実行するように「最善の努力」をするようにJVMに明示的に指示します。これにより、ガベージコレクタによってRemote
オブジェクトが再利用される可能性がありますRemote
オブジェクトへの強い参照がreleasedの場合。 _Java.rmi.NoSuchObjectException
_は、Remote
オブジェクトが再利用された後にクライアントが接続すると観察されます。-nogc
_ガベージコレクションを明示的に要求しません。これにより、強力な参照が保持されているか解放されているかに関係なく、Remote
オブジェクトはクライアントからアクセス可能なままになりますサーバーの起動とクライアントの呼び出しの間に十分なdelayがない限りシステムがガベージコレクターを「自然に」呼び出して、Remote
オブジェクトを回収すること。-hold
_Remote
オブジェクトへの強い参照を保持します。この場合、クラス変数はRemote
オブジェクトを参照します。-release
_(デフォルト)Remote
オブジェクトへの強い参照が解放されます。この場合、メソッド変数はRemote
オブジェクトを参照します。メソッドが戻った後、強参照は失われます。-delay<S>
_サーバーの起動とクライアントの呼び出しの間で待機する秒数。遅延を挿入すると、ガベージコレクターが「自然に」実行される時間が提供されます。これは、最初は「機能する」が、かなりの時間が経過すると失敗するプロセスをシミュレートします。秒数の前にスペースがないことに注意してください。例:_-delay5
_は、サーバーが起動してから5秒後にクライアントを呼び出します。System.gc()
のようなものは単なるヒントであり、_-delay<S>
_オプションの設定はガベージコレクターの動作に関する推測ゲームであるため、プログラムの動作はマシン間およびJVM間で異なる可能性があります。
私のマシンでは、_javac RMITest.Java
_をコンパイルした後、次のような動作が見られます。
_$ Java RMITest -nogc -hold
received: foo
$ Java RMITest -nogc -release
received: foo
$ Java RMITest -gc -hold
received: foo
$ Java RMITest -gc -release
Exception in thread "main" Java.rmi.NoSuchObjectException: no such object in table
at Sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.Java:255)
at Sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.Java:233)
at Sun.rmi.server.UnicastRef.invoke(UnicastRef.Java:142)
at Java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.Java:178)
at Java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.Java:132)
at $Proxy0.remoteOperation(Unknown Source)
at RMITest.client(RMITest.Java:69)
at RMITest.main(RMITest.Java:46)
_
ここにソースコードがあります:
_import Java.rmi.Remote;
import Java.rmi.RemoteException;
import Java.rmi.registry.LocateRegistry;
import Java.rmi.registry.Registry;
import Java.rmi.server.UnicastRemoteObject;
import static Java.util.concurrent.TimeUnit.*;
interface RemoteOperations extends Remote {
String remoteOperation() throws RemoteException;
}
public final class RMITest implements RemoteOperations {
private static final String REMOTE_NAME = RemoteOperations.class.getName();
private static final RemoteOperations classVariable = new RMITest();
private static boolean holdStrongReference = false;
private static boolean invokeGarbageCollector = true;
private static int delay = 0;
public static void main(final String... args) throws Exception {
for (final String arg : args) {
if ("-gc".equals(arg)) {
invokeGarbageCollector = true;
} else if ("-nogc".equals(arg)) {
invokeGarbageCollector = false;
} else if ("-hold".equals(arg)) {
holdStrongReference = true;
} else if ("-release".equals(arg)) {
holdStrongReference = false;
} else if (arg.startsWith("-delay")) {
delay = Integer.parseInt(arg.substring("-delay".length()));
} else {
System.err.println("usage: javac RMITest.Java && Java RMITest [-gc] [-nogc] [-hold] [-release] [-delay<seconds>]");
System.exit(1);
}
}
server();
if (invokeGarbageCollector) {
System.gc();
}
if (delay > 0) {
System.out.println("delaying " + delay + " seconds");
final long milliseconds = MILLISECONDS.convert(delay, SECONDS);
Thread.sleep(milliseconds);
}
client();
System.exit(0); // stop RMI server thread
}
@Override
public String remoteOperation() {
return "foo";
}
private static void server() throws Exception {
// This reference is eligible for GC after this method returns
final RemoteOperations methodVariable = new RMITest();
final RemoteOperations toBeStubbed = holdStrongReference ? classVariable : methodVariable;
final Remote remote = UnicastRemoteObject.exportObject(toBeStubbed, 0);
final Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
registry.bind(REMOTE_NAME, remote);
}
private static void client() throws Exception {
final Registry registry = LocateRegistry.getRegistry();
final Remote remote = registry.lookup(REMOTE_NAME);
final RemoteOperations stub = RemoteOperations.class.cast(remote);
final String message = stub.remoteOperation();
System.out.println("received: " + message);
}
}
_
考慮すべき他のいくつかの質問-最初にオブジェクトインスタンスを参照していますか、それともスタブインターフェイス自体がなくなっていますか?一部のオブジェクトインスタンスがなくなった場合、通常の理由で逆参照され、GCされますが、それがインターフェイスの場合、RMIサーバーのエンドポイントループは何らかの理由で終了します。
これまでに見つけた最高のデバッグツールは、Java.rmi.server.logCalls = trueプロパティをオンにすることです( http://Java.Sun.com/j2se/1.5.0/docs/guideを参照)。 /rmi/javarmiproperties.html )そして、すべてのすばらしい情報がログウィンドウに表示されます。これにより、毎回何が起きているかがわかります。
ジョス
私は同じ問題を抱えていますが、解決しました。解決策は簡単です。オブジェクトがGCされないようにするには、強い参照「オブジェクト」を作成する必要があります。
例えばあなたのサーバークラスで:
...
private static ServiceImpl serviceImpl = null;
public static void register (int port) {
serviceImpl = new ServiceImpl();
Registry registry = LocateRegistry.createRegistry(port);
registry.rebind ("serviceImpl", serviceImpl);
}
public static void main(String[] args) throws RemoteException, NotBoundException {
register(1099);
...the rest of your code...
}
したがって、「serviceImpl」オブジェクトがGCされるのを防ぎます。 CMIIW
上記の議論には欠けている点が1つあります。分散ガベージコレクション(DGC)と呼ばれるものがあります。分散オブジェクトへの有効なローカルおよびリモート参照がない場合、GCはメモリからオブジェクトを削除できます。これを検証する高度なアルゴリズムがあります。上記のニースコードスニペットは、DGCの有効性を実際に示す良い例です。
どういうわけか機能のように見えるものは、設計された動作に他なりません!
フランク
コードを見ずにこの質問に答えるのは難しいです(ここで公開できないほど大きいと思います)。ただし、Occamのかみそりを使用すると、2つの可能性があります。
上記の2つの点に注意して、コードパスを注意深く検討することをお勧めします。
Spring Remoting(RMI)を使用しているときに、このエラーに遭遇しました。私のサービスはガベージコレクションされませんでした。
「org.springframework」のデバッグログをオンにした後、クライアントが接続しようとしたポートではなく、デフォルトのポート(1099)でサーバーがサービスを登録していることがわかりました。
「Java.rmi.server.logCalls = true」は、クライアントが接続しようとしたときにサーバーにいくつかの出力を表示したため、ポートに関してはすべて問題ないと思いました。
このエラーが発生した場合は、ポート(サービスとレジストリの1つ)を再確認してください。