【问题标题】:java.rmi.NoSuchObjectException: no such object in tablejava.rmi.NoSuchObjectException:表中没有这样的对象
【发布时间】:2010-10-13 07:29:29
【问题描述】:

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

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

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

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

什么会导致这些异常?

【问题讨论】:

    标签: java exception rmi


    【解决方案1】:

    在使用弹簧远程处理 (rmi) 时,我遇到了这个错误。 我的服务没有被垃圾回收。

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

    我认为端口方面的一切都很好,因为当客户端尝试连接时,“java.rmi.server.logCalls=true”确实在服务器上显示了一些输出。

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

    【讨论】:

      【解决方案2】:

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

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

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

      • -gc(默认)明确指示 JVM “尽最大努力”在服务器启动后、客户端连接到服务器之前运行垃圾收集器。这可能会导致Remote 对象被垃圾收集器回收如果对Remote 对象的强引用被释放。回收Remote 对象后客户端连接时观察到java.rmi.NoSuchObjectException
      • -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);
          }
      }
      

      【讨论】:

      • 所以你说的是我需要(在服务器上)手动保留对我的服务器对象的引用,因为我放入注册表的导出的 UnicastRemoteObject 不会阻止该对象成为垃圾-collected,这会让我在注册表中留下一个悬空的引用?
      • 我希望在我“解除绑定”该对象之前,RMI 系统会使其保持活动状态。
      • 宾果游戏。保留引用的责任似乎落在了程序员身上。我对 RMI 系统在绑定生效时保持引用抱有同样的希望。可能有一个很好的理由让它成为这样(我没有研究过 RMI 来源)。无论如何,它似乎并不过分直观。
      • 这个答案不正确。它完全忽略了 DGC 的影响。远程对象的 DGC 客户端的存在足以防止它被本地 GC。在这种情况下,注册表成为远程对象的 DGC 客户端,因为它被提供了一个远程存根。这段代码的行为之所以如此,是因为 Registry 本身得到了 GC,它既取消导出它,又释放了它的所有绑定,这反过来又释放了它对远程对象的 DGC 持有,这在turn 允许远程对象在本地进行 GC。
      • @EJP 我有兴趣了解更多信息。请您再写一个脚本来展示您的想法并将其发布为该问题的另一个答案吗?
      【解决方案3】:

      我有同样的问题,现在我已经解决了。解决方案很简单,您必须创建强引用“对象”以避免对象被 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'd。 CMIIW

      【讨论】:

      • 您需要静态存储 Registry 引用。
      【解决方案4】:

      遇到同样的错误,但可能是其他(但未知的)原因。

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

      【讨论】:

      • 移除演员表并没有解决任何问题。 exportObject() 的结果是 Remote, 而不是 MyRemoteObject,,它实际上是一个存根,而不是提供的 Remote 对象参数。这段代码不可能编译,更别说执行了。
      【解决方案5】:

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

      看起来像是功能的东西只不过是设计的行为!

      弗兰克

      【讨论】:

        【解决方案6】:

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

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

        乔斯

        【讨论】:

          【解决方案7】:

          不看代码就很难回答这个问题(我猜代码足够大,无法在此处发布)。但是,使用奥卡姆剃刀,您有两种可能

          • 服务器对象必须以某种方式取消注册
          • 既然断点停止了错误,那肯定是一种竞争条件。

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

          【讨论】:

            猜你喜欢
            • 2015-08-16
            • 2019-02-09
            • 1970-01-01
            • 2016-09-22
            • 1970-01-01
            • 1970-01-01
            • 2016-03-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多