web-dev-qa-db-fra.com

java.rmi.NoSuchObjectException: aucun objet de ce type dans la table

J'écris un serveur RMI très simple et je vois un Java.rmi.NoSuchObjectExceptions intermittent dans les tests unitaires. 

J'ai une chaîne d'appels de méthodes distantes sur le même objet, et pendant que les premiers passent, les derniers échouent parfois. Je ne fais rien pour annuler l'inscription de l'objet serveur entre les deux.

Ces erreurs n'apparaissent pas toujours et si je mets des points d'arrêt, elles ont tendance à ne pas apparaître. Est-ce que ces Heisenbugs, dont les conditions de course se dissolvent quand on les regarde à travers l'exécution ralentie du débogueur? Il n'y a pas de multi-threading en cours dans mon code de test ou de serveur (même si c'est peut-être à l'intérieur de la pile RMI?).

Je l’utilise sous Mac OS X 10.5 (Java 1.5) via le plug-in JUnit d’Eclipse. Le serveur RMI et le client se trouvent tous deux dans la même machine virtuelle.

Qu'est-ce qui peut causer ces exceptions?

27
Thilo

Conservez une référence forte à l'objet qui implémente l'interface Java.rmi.Remote de sorte qu'il reste accessible , c'est-à-dire non admissible pour le garbage collection.

Ci-dessous, un programme court qui illustre un Java.rmi.NoSuchObjectException . Le script est autonome et crée un registre RMI ainsi qu’un "client" et un "serveur" dans une seule machine virtuelle.

Copiez simplement ce code et enregistrez-le dans un fichier nommé RMITest.Java. Compilez et appelez avec votre choix d'arguments en ligne de commande:

  • -gc (par défaut) Indique explicitement à la machine virtuelle Java de faire "tout son possible" pour exécuter le garbage collector après le démarrage du serveur, mais avant que le client ne se connecte au serveur. Cela entraînera probablement la récupération de l'objet Remote par le garbage collector si la référence forte à l'objet Remote est publiée. Un Java.rmi.NoSuchObjectException est observé lorsque le client se connecte après la récupération de l'objet Remote.
  • -nogc Ne pas demander explicitement une récupération de place. Cela rendra probablement l'objet Remote accessible pour le client, qu'une référence forte soit maintenue ou libérée à moins qu'il y ait un delay suffisant entre le démarrage du serveur et l'appel du client, de sorte que le système "fonctionne "invoque le ramasse-miettes et récupère l'objet Remote}.
  • -hold Conserve une référence forte à l'objet Remote. Dans ce cas, une variable de classe fait référence à l'objet Remote.
  • -release (par défaut) Une référence forte à l'objet Remote sera publiée. Dans ce cas, une variable de méthode fait référence à l'objet Remote. Après le retour de la méthode, la référence forte est perdue.
  • -delay<S> Nombre de secondes à attendre entre le démarrage du serveur et l'appel du client. L'insertion d'un délai donne au récupérateur de mémoire le temps de s'exécuter "naturellement". Cela simule un processus qui "fonctionne" initialement, mais échoue au bout d'un certain temps. Notez qu'il n'y a pas d'espace avant le nombre de secondes. Exemple: -delay5 fera l'appel client 5 secondes après le démarrage du serveur.

Le comportement du programme variera probablement d'une machine à l'autre et d'une machine à l'autre, car des éléments tels que System.gc() ne sont que des astuces et la définition de l'option -delay<S> est un jeu de devinettes en ce qui concerne le comportement du garbage collector.

Sur ma machine, après javac RMITest.Java à compiler, je constate ce comportement:

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

Voici le code source:

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);
    }
}
65
Greg Mattes

Quelques autres questions à prendre en compte - Commencez-vous par référencer une instance d'objet ou l'interface de stub est-elle partie? Si une instance d'objet a disparu, pour les raisons habituelles, elle a été déréférencée et référencée, mais s'il s'agit de l'interface, la boucle de noeud final du serveur RMI s'est arrêtée pour une raison quelconque.

Le meilleur outil de débogage que j'ai trouvé jusqu'à présent consiste à activer la propriété Java.rmi.server.logCalls = true (voir http://Java.Sun.com/j2se/1.5.0/docs/guide/rmi /javarmiproperties.html ) et regardez toutes les informations merveilleuses qui défilent dans votre fenêtre de journal. Cela me dit ce qui se passe à chaque fois.

jos

8
jottos

J'ai le même problème et maintenant je l'ai résolu. La solution est simple, vous DEVEZ créer un «objet» de référence fort pour éviter que l’objet ne soit converti.

par exemple dans votre classe de serveur:

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

Ainsi, il empêche l'objet "serviceImpl" d'être traité par GC. CMIIW

2
Fahmi

il manque un point à la discussion ci-dessus. Il y a quelque chose qui s'appelle la collecte de déchets distribuée (DGC). S'il n'existe aucune référence locale ou distante à un objet distribué, le CPG est autorisé à supprimer l'objet de la mémoire. Il existe un algorithme sophistiqué pour le vérifier. L’extrait de code de Nice d’en haut est en effet une bonne démonstration de l’efficacité de la GCR.

Ce qui ressemble en quelque sorte à une fonctionnalité n’est autre que le comportement conçu! 

Franc

1
Frank Z

Il est difficile de répondre à cette question sans regarder le code (qui, je suppose, sera assez gros pour ne pas être publiable ici). Cependant, en utilisant le rasoir d'Occam, vous avez deux possibilités

  • Les objets serveur doivent être désenregistrés
  • Puisque les points d'arrêt éliminent les erreurs, il s'agit certainement d'une situation de concurrence critique.

Je vous suggère de parcourir les chemins de code avec précaution en gardant à l'esprit les deux points ci-dessus.

0
talonx

En utilisant Spring Remoting (RMI), je me suis heurté à cette erreur… .. Mon service n'a pas été récupéré.

Après avoir activé la journalisation de débogage pour "org.springframework", j'ai découvert que mon serveur enregistrait le service sur le port par défaut (1099) au lieu du port auquel le client essayait de se connecter.

Je pensais que tout était bon pour le port car "Java.rmi.server.logCalls = true" affichait une sortie sur le serveur lorsque le client essayait de se connecter. 

Lorsque vous obtenez cette erreur, vérifiez les ports (celui du service et celui du registre).

0
Tinus Tate