Pregunta

Estoy escribiendo un servidor RMI muy simple y veo intermitentes java.rmi.NoSuchObjectExceptions en las pruebas unitarias.

Tengo una serie de llamadas a métodos remotos en el mismo objeto, y aunque las primeras pasan, las últimas algunas veces fallan. No estoy haciendo nada para anular el registro del objeto del 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 raza se disuelven al mirarlos a través de la ejecución lenta del depurador? No hay subprocesos múltiples en mi código de prueba o servidor (aunque ¿quizás dentro de la pila RMI?).

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

¿Qué puede causar estas excepciones?

¿Fue útil?

Solución

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

A continuación se muestra un breve programa que muestra un <= > . El script es autónomo y crea un registro RMI, así como un & Quot; client & Quot; y un " servidor " en una sola JVM.

Simplemente copie este código y guárdelo en un archivo llamado java.rmi.NoSuchObjectException. Compile e invoque con su elección de argumentos de línea de comando:

  • RMITest.java (predeterminado) Indique explícitamente a la JVM que haga " un mejor esfuerzo " para ejecutar el recolector de basura después de que se inicie el servidor, pero antes de que el cliente se conecte al servidor. Esto probablemente hará que el objeto -gc sea reclamado por el recolector de basura si la referencia fuerte al objeto Remote es liberada . Se observa un -nogc cuando el cliente se conecta después de reclamar el objeto -hold.
  • -release No solicite explícitamente la recolección de basura. Esto probablemente hará que el cliente mantenga accesible el objeto -delay<S> 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 del cliente. que el sistema " naturalmente " invoca al recolector de basura y reclama el -delay5 objeto .
  • System.gc() Conserva una referencia fuerte al objeto javac RMITest.java. En este caso, una variable de clase se refiere al objeto <=>.
  • <=> (predeterminado) Se lanzará una referencia fuerte al objeto <=>. En este caso, una variable de método se refiere al objeto <=>. Después de que el método regresa, se pierde la referencia fuerte.
  • <=> El número de segundos para esperar entre el inicio del servidor y la llamada del cliente. Insertar un retraso proporciona tiempo para que el recolector de basura ejecute & Quot; naturalmente. & Quot; Esto simula un proceso que & Quot; funciona & Quot; inicialmente, pero falla después de que haya pasado un tiempo significativo. Tenga en cuenta que no hay espacio antes de la cantidad de segundos. Ejemplo: <=> hará que el cliente llame 5 segundos después de que se inicie el servidor.

El comportamiento del programa probablemente variará de una máquina a otra y de JVM a JVM porque cosas como <=> son solo sugerencias y configurar la opción <=> es un juego de adivinanzas con respecto al comportamiento del recolector de basura.

En mi máquina, después de <=> 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á el código fuente:

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);
    }
}

Otros consejos

Algunas otras preguntas a tener en cuenta: primero, ¿hace referencia a una instancia de objeto o la interfaz stub en sí misma se ha ido? Si alguna instancia de objeto se ha ido, es por las razones habituales, se ha desreferenciado y GC, pero si es la interfaz, el bucle de punto final del servidor RMI se cierra por alguna razón.

La mejor herramienta de depuración que he encontrado hasta ahora es activar la propiedad java.rmi.server.logCalls = true (consulte http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html ) y vea toda la maravillosa información fluir por su ventana de registro. Esto me dice qué pasa cada vez.

jos

Tengo el mismo problema y ahora lo he resuelto. La solución es simple, DEBE crear un 'objeto' de referencia fuerte 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 " serviceImpl " objeto de ser GC'd. CMIIW

falta un punto 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, el GC puede eliminar el objeto de la memoria. Hay un algoritmo sofisticado para verificar esto. El buen fragmento de código de arriba es una buena demostración de la efectividad de la DGC.

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

Frank

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

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

Te sugiero que repases las rutas del código cuidadosamente teniendo en cuenta los dos puntos anteriores.

Al usar Spring Remoting (rmi) me topé con este error. Mi servicio no era recolección de basura.

Después de activar el registro de depuración para " org.springframework " descubrí que mi servidor estaba registrando el servicio en el puerto predeterminado (1099) en lugar del puerto al que el cliente intentaba conectarse.

Pensé que todo lo relacionado con el puerto estaba bien porque " java.rmi.server.logCalls = true " mostró algunos resultados en el servidor cuando el cliente intentaba conectarse.

Al obtener este error, verifique dos veces los puertos (el servicio y el registro).

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

Estaba enviando el objeto exportado al tipo de mi interfaz remota y luego, al vincular el nombre, obtenía NoSuchObjectException. La eliminación del casting 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);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top