java.rmi.NoSuchObjectException:表中没有这样的对象

时间:2009-03-14 02:24:12

标签: java exception rmi

我正在编写一个非常简单的RMI服务器,我在单元测试中看到间歇java.rmi.NoSuchObjectExceptions

我在同一个对象上有一串远程方法调用,而前几个调用时,后面的调用有时会失败。我没有做任何事情来取消注册服务器对象。

这些错误并不总是出现,如果我输入断点,它们往往不会出现。是那些Heisenbugs,当通过调试器的执行速度减慢来查看它们时,它们的竞争条件会消失吗?我的测试或服务器代码中没有多线程(虽然可能在RMI堆栈内部?)。

我是通过Eclipse的JUnit插件在Mac OS X 10.5(Java 1.5)上运行它,而RMI服务器和客户端都在同一个JVM中。

什么可能导致这些例外?

7 个答案:

答案 0 :(得分:66)

强制引用实现java.rmi.Remote接口的对象,使其保持reachable,即不符合垃圾回收的条件。

以下是一个演示java.rmi.NoSuchObjectException的简短程序。该脚本是自包含的,在单个JVM中创建RMI注册表以及“客户端”和“服务器”。

只需复制此代码并将其保存在名为RMITest.java的文件中即可。使用您选择的命令行参数进行编译和调用:

  • -gc (默认)明确指示JVM在服务器启动后但在客户端连接到服务器之前“尽最大努力”运行垃圾收集器。如果对Remote对象的强引用已释放 ,这可能会导致垃圾收集器回收Remote对象。在回收java.rmi.NoSuchObjectException对象后,客户端连接时会观察到Remote
  • -nogc 不要显式请求垃圾回收。这可能会导致客户端仍然可以访问Remote对象,无论是否保留或释放强引用,除非服务器启动和客户端之间有足够的延迟调用,以便系统“自然地”调用垃圾收集器并回收Remote对象
  • -hold 保留对Remote对象的强引用。在这种情况下,类变量引用Remote对象。
  • -release (默认)将发布对Remote对象的强引用。在这种情况下,方法变量引用Remote对象。方法返回后,强引用将丢失。
  • -delay<S> 服务器启动和客户端调用之间等待的秒数。插入延迟为垃圾收集器“自然地”运行提供了时间。这模拟了一个最初“有效”的过程,但在经过一段时间后失败了。请注意,秒数之前没有空格。示例:-delay5将在服务器启动后5秒进行客户端调用。

程序行为可能因机器和JVM到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);
    }
}

答案 1 :(得分:8)

要考虑的其他一些问题 - 首先是您引用对象实例还是存根接口本身已经消失了?如果某个对象实例消失了,由于通常的原因,它被取消引用并且GC了,但是如果它是接口,则由于某种原因你的RMI服务器端点循环退出。

我到目前为止找到的最好的调试工具是打开java.rmi.server.logCalls = true属性(参见http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html) 并在日志窗口中查看所有精彩信息流。这告诉我每次都有什么。

答案 2 :(得分:2)

我有同样的问题,现在我已经解决了。解决方案很简单,你必须创建强大的参考对象&#39;避免对象是GC&#39; d。

例如在您的服务器类中:

...
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...
}

因此,它保护&#34; serviceImpl&#34;来自GC的对象&#39; d。 CMIIW

答案 3 :(得分:1)

上述讨论中缺少一点。有一种叫做分布式垃圾收集(DGC)的东西。如果没有对分布式对象的实时本地和远程引用,则允许GC从内存中删除该对象。有一个复杂的算法来验证这一点。上面的优秀代码片段确实证明了DGC的有效性。

什么看起来像一个功能只是设计的行为!

答案 4 :(得分:0)

如果不看代码就很难回答这个问题(我猜这个代码很大,不能在这里公布)。但是,使用奥卡姆剃刀,你有两个可能性

  • 服务器对象必须以某种方式取消注册
  • 由于断点会阻止错误,因此肯定是竞争条件。

我建议你仔细考虑代码路径,牢记两点。

答案 5 :(得分:0)

在使用弹簧远程(rmi)时,我遇到了此错误。 我的服务不是垃圾收集。

打开“ org.springframework”的调试日志后,我发现我的服务器正在默认端口(1099)上注册该服务,而不是客户端尝试连接的端口。

我认为一切明智的做法都可以,因为“ java.rmi.server.logCalls = true”确实在客户端尝试连接时在服务器上显示了一些输出。

出现此错误时,请仔细检查端口(服务和注册表之一)。

答案 6 :(得分:-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);
}