Java.rmi.NoSuchObjectException: no hay tal objeto en la tabla


Estoy escribiendo un servidor RMI muy simple, y estoy viendo java.rmi.NoSuchObjectExceptions intermitente en las pruebas unitarias.

Tengo una cadena de llamadas a métodos remotos en el mismo objeto, y mientras los primeros pasan, los últimos a veces fallan. No estoy haciendo nada para anular el registro del objeto servidor en el medio.

Estos errores no aparecen siempre, y si pongo puntos de interrupción tienden a no aparecer. Son esos Heisenbugs, cuyas condiciones de carrera se disuelven al mirarlos a través del ralentizado la ejecución del depurador? No hay multi-threading pasando en mi prueba o código de servidor (aunque tal vez dentro de la pila de RMI?).

Estoy ejecutando esto en Mac OS X 10.5 (Java 1.5) a través del complemento JUnit de Eclipse, y el servidor RMI y el cliente están en la misma JVM.

¿Qué puede causar estas excepciones?

Author: Greg Mattes, 2009-03-14

6 answers

Mantenga una referencia fuerte al objeto que implementa la interfaz java.rmi.Remote para que siga siendo accesible, es decir, no elegible para la recolección de basura.

A continuación se muestra un programa corto que demuestra un java.rmi.NoSuchObjectException. El script es autónomo, creando un registro RMI, así como un" cliente "y un" servidor " en una sola JVM.

Simplemente copie este código y guárdelo en un archivo llamado RMITest.java. Compilar e invocar con su elección de línea de comandos argumentos:

  • -gc (predeterminado) Indica explícitamente a la JVM que haga "el mejor esfuerzo" para ejecutar el recolector de basura después de iniciar el servidor, pero antes de que el cliente se conecte al servidor. Esto probablemente causará que el objeto Remote sea reclamado por el recolector de basura si la referencia fuerte al objeto Remote es liberada. Se observa un java.rmi.NoSuchObjectException cuando el cliente se conecta después de recuperar el objeto Remote.
  • -nogc no solicitar explícitamente la recolección de basura. Esto probablemente causará que el objeto Remote permanezca accesible para el cliente, independientemente de si se mantiene o libera una referencia fuerte a menos que haya un retraso suficiente entre el inicio del servidor y la llamada al cliente, de modo que el sistema invoque "naturalmente" al recolector de basura y reclame el objeto Remote .
  • -hold Mantenga una referencia fuerte al objeto Remote. En este caso, una variable de clase se refiere a la Remote objeto.
  • -release (por defecto) Se liberará una referencia fuerte al objeto Remote. En este caso, una variable de método se refiere al objeto Remote. Después de que el método regresa, la referencia fuerte se pierde.
  • -delay<S> El número de segundos a esperar entre el inicio del servidor y la llamada del cliente. Insertar un retardo proporciona tiempo para que el recolector de basura se ejecute "naturalmente"."Esto simula un proceso que" funciona " inicialmente, pero falla después de un tiempo significativo ha pasado. Nota no hay espacio antes del número de segundos. Ejemplo: -delay5 hará la llamada al cliente 5 segundos después de que se inicie el servidor.

El comportamiento del programa probablemente variará de máquina a máquina y JVM a JVM porque cosas como System.gc() son solo sugerencias y configurar la opción -delay<S> es un juego de adivinanzas con respecto al comportamiento del recolector de basura.

En mi máquina, después de javac RMITest.java compilar, veo este comportamiento:

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

Aquí está la fuente código:

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);
    }
}
 64
Author: Greg Mattes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-23 15:30:11

Algunas otras preguntas a considerar - Primero está haciendo referencia a una instancia de objeto o se ha ido la propia interfaz de stub? Si alguna instancia de objeto se ha ido, es por las razones habituales, se desreferenció y GC'd, pero si es la interfaz, entonces el bucle de punto final del servidor RMI se cerró por alguna razón.

La mejor herramienta de depuración que he encontrado hasta ahora es activar java.rmi.servidor.logCalls = true property (ver http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html ) y mira toda la maravillosa información que fluye por tu ventana de registro. Esto me dice lo que pasa cada vez.

Jos

 8
Author: jottos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-14 08:58:55

Tengo el mismo problema y ahora lo he resuelto. La solución es simple, debe crear una referencia fuerte 'object' para evitar que el objeto sea GC'd.

Por ejemplo en su clase de servidor:

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

Por lo tanto, protege el objeto "ServiceImpl" de ser GC'd. CMIIW

 2
Author: Fahmi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-24 03:06:45

Hay un punto que falta en la discusión anterior. Hay algo que se llama recolección de basura distribuida (DGC). Si no hay referencias locales y remotas vivas a un objeto distribuido, se permite a GC eliminar el objeto de la memoria. Hay un algoritmo sofisticado para verificar esto. El bonito fragmento de código de arriba es de hecho una buena demostración de la eficacia de la DGC.

Lo que de alguna manera parece una característica no es más que el comportamiento diseñado!

Frank

 1
Author: Frank Z,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-04 07:07:13

Es difícil responder a esta pregunta sin mirar el código (que supongo que será lo suficientemente grande como para no publicarse aquí). Sin embargo, usando la navaja de Occam, usted tiene dos posibilidades

  • Los objetos del servidor deben quedar sin registrar de alguna manera
  • Dado que los puntos de interrupción detienen los errores, es definitivamente una condición de carrera.

Le sugeriría que repase las rutas de código cuidadosamente teniendo en cuenta los dos puntos anteriores.

 0
Author: talonx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-14 04:51:40

Tiene el mismo error, pero probablemente por la otra razón (aún desconocida).

Estaba lanzando el objeto exportado al tipo de mi interfaz remota y luego, mientras se vinculaba a name, obtenía NoSuchObjectException. La eliminación de la fundición solucionó el problema.

Brevemente:

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);
}
 -1
Author: TheJanOnline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-20 11:15:18