java.rmi.NoSuchObjectException: нет такого объекта в таблице - PullRequest
27 голосов
/ 14 марта 2009

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

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

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

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

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

Ответы [ 7 ]

66 голосов
/ 12 мая 2009

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

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

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

  • -gc (по умолчанию) Явно проинструктируйте JVM приложить «максимальные усилия» для запуска сборщика мусора после запуска сервера, но до того, как клиент подключится к серверу. Это может привести к тому, что сборщик мусора восстановит объект Remote, если сильная ссылка на объект Remote равна release . 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);
    }
}
8 голосов
/ 14 марта 2009

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

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

Jos

2 голосов
/ 24 сентября 2014

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

1 голос
/ 04 февраля 2011

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

То, что как-то похоже на особенность, есть не что иное, как разработанное поведение!

Frank

0 голосов
/ 21 декабря 2018

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

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

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

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

0 голосов
/ 20 июля 2014

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

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

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

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

Я бы посоветовал вам внимательно изучить пути кода, помня о двух вышеизложенных моментах.

...