Подтвердить что ты не робот

Java.rmi.NoSuchObjectException: нет такого объекта в таблице

Я пишу очень простой сервер RMI, и я вижу прерывистый java.rmi.NoSuchObjectExceptions в модульных тестах.

У меня есть строка удаленных вызовов методов на одном и том же объекте, и в то время как первые несколько пройдут, более поздние будут иногда терпеть неудачу. Я ничего не делаю, чтобы отменить регистрацию объекта сервера между ними.

Эти ошибки не появляются всегда, и если я помещаю точки останова, они, как правило, не отображаются. Являются ли эти Heisenbugs, условия гонки которых растворяются при просмотре их через замедленное выполнение отладчика? В моем тестовом или серверном коде много многопоточности (хотя, возможно, внутри стека RMI?).

Я запускаю это в Mac OS X 10.5 (Java 1.5) через плагин Eclipse JUnit, а сервер RMI и клиент находятся в одной JVM.

Что может вызывать эти исключения?

4b9b3361

Ответ 1

Хранить сильную ссылку на объект, реализующий интерфейс java.rmi.Remote, чтобы он оставался достижимым, то есть не имеющим права на сбор мусора.

Ниже приведена короткая программа, демонстрирующая java.rmi.NoSuchObjectException. script является автономным, создавая реестр RMI, а также "клиент" и "сервер" в одной JVM.

Просто скопируйте этот код и сохраните его в файле с именем RMITest.java. Скомпилируйте и вызовите с помощью ваших аргументов командной строки:

  • -gc (по умолчанию) Явное указание JVM сделать "лучшее усилие" для запуска сборщика мусора после запуска сервера, но до того, как клиент подключится к серверу. Вероятно, это приведет к тому, что объект Remote будет возвращен сборщиком мусора, если сильная ссылка на объект Remote будет выпущена. A 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);
    }
}

Ответ 2

Некоторые другие вопросы, которые нужно рассмотреть: во-первых, вы ссылаетесь на экземпляр объекта или сам интерфейс заглушки? Если какой-то экземпляр объекта исчез, его по обычным причинам он получил разыменованный и GC'd, но если это интерфейс, то конечная точка сервера RMI-сервера перестает работать по какой-либо причине.

Лучшим инструментом отладки, который я нашел до сих пор, является включение свойства java.rmi.server.logCalls = true (см. http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html) и наблюдайте за всеми замечательными потоками информации в вашем окне журнала. Это говорит мне, что каждый раз.

Jos

Ответ 3

У меня такая же проблема, и теперь я ее решил. Решение прост, вы ДОЛЖНЫ создать сильный ссылочный "объект", чтобы исключить объект 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'd. CMIIW

Ответ 4

в приведенном выше обсуждении отсутствует одна точка. Есть что-то, что называется распределенной сборкой мусора (DGC). Если нет живых локальных и удаленных ссылок на распределенный объект, GC разрешено удалять объект из памяти. Для этого существует сложный алгоритм. Хороший фрагмент кода сверху действительно является хорошей демонстрацией эффективности DGC.

Что-то похоже на функцию - это не что иное, как продуманное поведение!

Франк

Ответ 5

Трудно ответить на этот вопрос, не глядя на код (который, я думаю, будет достаточно большим, чтобы его нельзя было публиковать здесь). Однако, используя бритву Occam, у вас есть две возможности

  • Серверные объекты должны незарегистрироваться как-то
  • Так как точки останова останавливают ошибки, это определенно условие гонки.

Я бы посоветовал вам переходить по кодам, тщательно соблюдая приведенные выше две точки.

Ответ 6

При использовании весеннего удаленного взаимодействия (rmi) я столкнулся с этой ошибкой. Мой сервис не был мусором.

После включения ведения журнала отладки для "org.springframework" я обнаружил, что мой сервер регистрирует службу на порту по умолчанию (1099) вместо порта, к которому пытается подключиться клиент.

Я думал, что все порты были в порядке, потому что "java.rmi.server.logCalls = true" действительно показал некоторые выходные данные на сервере, когда клиент пытался подключиться.

При получении этой ошибки дважды проверьте порты (сервис и реестр один).

Ответ 7

Получена та же ошибка, но, вероятно, для другой (пока неизвестной) причины.

Я передавал экспортированный объект в тип моего удаленного интерфейса, а затем при привязке к имени я получал 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);
}