28

我正在编写一个非常简单的 RMI 服务器,并且java.rmi.NoSuchObjectExceptions在单元测试中看到断断续续。

我在同一个对象上有一串远程方法调用,虽然前几个通过,但后来的有时会失败。我没有做任何事情来取消注册两者之间的服务器对象。

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

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

什么会导致这些异常?

4

7 回答 7

68

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

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

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

  • -gc (default) Explicitly instruct the JVM to make "a best effort" to run the garbage collector after the server is started, but before the client connects to the server. This will likely cause the Remote object to be reclaimed by the garbage collector if the strong reference to the Remote object is released. A java.rmi.NoSuchObjectException is observed when the client connects after the Remote object is reclaimed.
  • -nogc Do not explicitly request garbage collection. This will likely cause the Remote object to remain accessible by the client regardless of whether a strong reference is held or released unless there is a sufficient delay between the server start and the client call such that the system "naturally" invokes the garbage collector and reclaims the Remote object.
  • -hold Retain a strong reference to the Remote object. In this case, a class variable refers to the Remote object.
  • -release (default) A strong reference to the Remote object will be released. In this case, a method variable refers to the Remote object. After the method returns, the strong reference is lost.
  • -delay<S> The number of seconds to wait between server start and the client call. Inserting a delay provides time for the garbage collector to run "naturally." This simulates a process that "works" initially, but fails after some significant time has passed. Note there is no space before the number of seconds. Example: -delay5 will make the client call 5 seconds after the server is started.

Program behavior will likely vary from machine to machine and JVM to JVM because things like System.gc() are only hints and setting the -delay<S> option is a guessing game with respect to the behavior of the garbage collector.

On my machine, after javac RMITest.java to compile, I see this behavior:

$ 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)

Here is the source code:

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

I have the same problem and now I've solved it. The solution is simple, you MUST create strong reference 'object' to avoid the object being GC'd.

for example in your server class:

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

So, it protects "serviceImpl" object from being GC'd. CMIIW

于 2014-09-24T03:06:45.977 回答
1

there is one point missing in the above discussion. There is something that is called distributed garbage collection (DGC). If there are no living local and remote references to a distributed object the GC is allowed to remove the object from memory. There is a sophisticated algorithm to verify this. The nice code snippet from above is indeed a good demonstration of the effectiveness of the DGC.

What somehow looks like a feature is nothing but the designed behavior!

Frank

于 2011-02-04T01:25:27.980 回答
0

不看代码就很难回答这个问题(我想代码会大到不能在这里发布)。然而,使用奥卡姆剃刀,你有两种可能

  • 服务器对象必须以某种方式取消注册
  • 由于断点停止了错误,这绝对是一个竞争条件。

我建议您仔细检查代码路径,牢记以上两点。

于 2009-03-14T04:51:40.940 回答
0

While using spring remoting (rmi) i bumped into this error. My service wasn't garbage collected.

After turning on debug logging for "org.springframework" i discovered that my server was registering the service on the default port (1099) instead of the port the client was trying to connect to.

I thought everything port wise was ok cause "java.rmi.server.logCalls=true" did show some output on server when client was trying to connect.

When getting this error double check the ports (the service and registry one).

于 2018-12-21T09:01:20.660 回答
-1

Got the same error but probably for the other (yet unknown) reason.

I was casting exported object to the type of my remote interface and then while binding to name I was getting NoSuchObjectException. Removing casting fixed the problem.

Briefly:

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 回答