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

我正在编写一个非常简单的RMI服务器,并且在unit testing中看到了间歇性的java.rmi.NoSuchObjectExceptions

我在同一个对象上有一串远程方法调用,而最初的几个经过,后面的有时会失败。 我没有做任何事情来注销之间的服务器对象。

这些错误并不总是出现,如果我把它们放在断点,他们往往不会出现。 这些Heisenbugs,当它们通过debugging器的执行速度减慢来看待它们时,它们的竞争状态会消失吗? 在我的testing或服务器代码中没有multithreading(虽然也许在RMI堆栈里面)?

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

什么会导致这些exception?

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

下面是一个演示java.rmi.NoSuchObjectException的简短程序。 该脚本是独立的,在单个JVM中创buildRMIregistry以及“客户端”和“服务器”。

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

  • -gc (默认值)显式地指示JVM在服务器启动之后,但在客户机连接到服务器之前尽最大努力运行垃圾回收器。 如果释放Remote对象的强引用,这可能会导致Remote对象被垃圾回收器回收。 在Remote对象被回收后,客户端连接时会观察到java.rmi.NoSuchObjectException
  • -nogc不显式请求垃圾回收。 这可能会导致Remote对象保持被客户端访问,无论强引用是被持有还是被释放, 除非在服务器启动和客户端调用之间有足够的延迟 ,使得系统“自然地”调用垃圾收集器并回收Remote对象
  • -hold保留对Remote对象的强烈引用。 在这种情况下,类variables引用Remote对象。
  • -release (默认)对Remote对象的强引用将被释放。 在这种情况下,一个方法variables引用了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'd,但是如果它是接口,那么你的RMI服务器端点循环由于某种原因退出。

到目前为止,我发现的最好的debugging工具是打开java.rmi.server.logCalls = true属性(请参阅http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties .html )并观看日志窗口中的所有奇妙信息stream。 这告诉我每次都是怎么回事

乔斯

上述讨论中有一点缺失。 有一种叫做分布式垃圾收集(DGC)的东西。 如果不存在对分布式对象的本地和远程引用,则允许GC从内存中移除该对象。 有一个复杂的algorithm来validation这一点。 上面的好的代码片段确实是DGC有效性的一个很好的例子。

不知何故看起来像一个function只是devise的行为!

坦率

如果没有看代码(我猜这个代码大到不能在这里发布),回答这个问题是很困难的。 但是,使用奥卡姆的razor,你有两个可能性

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

我build议你仔细阅读代码path,牢记上述两点。

我有同样的问题,现在我已经解决了。 解决方法很简单,你必须创build强引用“对象”,以避免对象被GC'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... } 

所以,它保护了“serviceImpl”对象不被GC。 CMIIW

得到了同样的错误,但可能为其他(但未知)的原因。

我正在将导出的对象转换为远程接口的types,然后绑定到名称时我得到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); }