28

java.rmi.NoSuchObjectExceptions非常に単純なRMIサーバーを作成していますが、単体テストで断続的に表示されます。

同じオブジェクトに対して一連のリモートメソッド呼び出しがあり、最初のいくつかは通過しますが、後のメソッドは失敗することがあります。その間にサーバーオブジェクトの登録を解除するために何もしていません。

これらのエラーは常に表示されるわけではなく、ブレークポイントを設定すると表示されない傾向があります。それらのHeisenbugsは、デバッガーの実行速度が低下していることを確認すると競合状態が解消されますか?テストコードまたはサーバーコードでマルチスレッドが実行されていません(ただし、RMIスタック内にある可能性があります)。

これをMacOSX 10.5(Java 1.5)でEclipseのJUnitプラグインを介して実行しており、RMIサーバーとクライアントは両方とも同じJVMにあります。

これらの例外の原因は何ですか?

4

7 に答える 7

68

インターフェイスを実装するオブジェクトへの強い参照を保持して、到達可能java.rmi.Remoteなままにする(つまり、ガベージ コレクションの対象外にする)。

以下は、java.rmi.NoSuchObjectException. このスクリプトは自己完結型で、単一の JVM に RMI レジストリと「クライアント」および「サーバー」を作成します。

このコードをコピーして、 という名前のファイルに保存するだけですRMITest.java。選択したコマンド ライン引数でコンパイルして呼び出します。

  • -gc(デフォルト) サーバーの起動後、クライアントがサーバーに接続する前に、ガベージ コレクターを実行するように "最善の努力" を行うように JVM に明示的に指示します。これにより、オブジェクトへの強い参照解放さRemoteれると、ガベージ コレクターによってオブジェクトが再利用される可能性があります。オブジェクトが再利用された後にクライアントが接続すると、 Aが観察されます。Remotejava.rmi.NoSuchObjectExceptionRemote
  • -nogcガベージ コレクションを明示的に要求しないでください。これRemoteにより、サーバーの起動とクライアントの呼び出しの間に十分な遅延がなく、システムがガベージ コレクターを「自然に」呼び出して、Remoteオブジェクト
  • -holdオブジェクトへの強い参照を保持しRemoteます。この場合、クラス変数はRemoteオブジェクトを参照します。
  • -release(デフォルト)Remoteオブジェクトへの強い参照が解放されます。この場合、メソッド変数はRemoteオブジェクトを参照します。メソッドが戻った後、強い参照は失われます。
  • -delay<S>サーバーの起動からクライアントの呼び出しまでに待機する秒数。遅延を挿入すると、ガベージ コレクターが「自然に」実行される時間が提供されます。これは、最初は「機能する」が、かなりの時間が経過すると失敗するプロセスをシミュレートします。秒数の前にスペースがないことに注意してください。例:-delay5サーバーが起動してから 5 秒後にクライアントを呼び出します。

プログラムの動作はマシンごと、JVM ごとに異なる可能性があります。これは、オプションの設定はガベージ コレクターの動作に関する推測ゲームであり、System.gc()ヒントにすぎないためです。-delay<S>

私のマシンでは、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);
    }
}
于 2009-05-12T18:17:13.170 に答える
8

考慮すべきその他の質問 - まず、オブジェクト インスタンスを参照していますか、それともスタブ インターフェイス自体がなくなっていますか? オブジェクト インスタンスがなくなった場合は、通常の理由で参照解除されて GC されますが、それがインターフェイスの場合は、何らかの理由で RMI サーバーのエンド ポイント ループが終了します。

これまでに見つけた最適なデバッグ ツールは、java.rmi.server.logCalls=true プロパティをオンにすることです ( http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmipropertiesを参照)。 .html ) を参照して、すべての素晴らしい情報がログ ウィンドウに表示されるのを確認してください。これは毎回何が起きているかを教えてくれます。

ジョス

于 2009-03-14T08:58:55.340 に答える
2

私は同じ問題を抱えており、今では解決しました。解決策は簡単です。オブジェクトが 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

于 2014-09-24T03:06:45.977 に答える
1

上記の議論で欠けている点が1つあります。分散ガベージコレクション(DGC)と呼ばれるものがあります。分散オブジェクトへのローカルおよびリモート参照が存在しない場合、GCはオブジェクトをメモリから削除できます。これを検証するための高度なアルゴリズムがあります。上からの素晴らしいコードスニペットは、確かにDGCの有効性の良いデモンストレーションです。

どういうわけか機能のように見えるのは、設計された動作に他なりません!

フランク

于 2011-02-04T01:25:27.980 に答える
0

コードを見ずにこの質問に答えるのは難しいです (コードはここで公開できないほど大きいと思います)。ただし、オッカムのカミソリを使用すると、2 つの可能性があります。

  • サーバー オブジェクトは何らかの形で登録解除されている必要があります
  • ブレークポイントがエラーを停止するため、これは間違いなく競合状態です。

上記の 2 つの点を念頭に置いて、コード パスを慎重に検討することをお勧めします。

于 2009-03-14T04:51:40.940 に答える
-1

同じエラーが発生しましたが、おそらく他の (まだ不明な) 理由によるものです。

エクスポートされたオブジェクトをリモート インターフェイスの型にキャストしていたのですが、名前にバインドしているときに NoSuchObjectException を取得していました。キャストを削除すると問題が解決しました。

簡単に言うと:

public interface MyRemoteInterface extedns Remote {
    ...
}

public class MyRemoteObject implements MyRemoteInterface {
    ...
}

public static MyRemoteObject obj = new MyRemoteObject();

public static void main(String[] args) {
    //removing cast to MyRemoteInterface fixes the problem
    this.obj = UnicastRemoteObject.exportObject((MyRemoteInterface) this.obj, 0);

    //unless the above cast is removed, this throws NoSuchObjectException occasionally
    LocateRegisry.getRegistry("127.0.0.1", 1099).bind("name", this.obj);
}
于 2014-07-20T11:15:18.540 に答える