Question

Je suis en train d’écrire un serveur RMI très simple, et je vois des signes intermittents java.rmi.NoSuchObjectExceptions dans les tests unitaires.

J'ai une chaîne d'appels de méthodes distantes sur le même objet et, bien 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 en les regardant à travers l'exécution ralentie du débogueur? Il n’ya pas de multi-threading dans mon test ou mon code serveur (même si 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?

Était-ce utile?

La solution

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

Vous trouverez ci-dessous un court programme présentant un <= > . Le script est autonome et crée un registre RMI ainsi qu'un & Quot; client & Quot; et un " serveur " dans une seule machine virtuelle.

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

  • RMITest.java (par défaut) Indique explicitement à la machine virtuelle Java de faire & un meilleur effort & "; pour exécuter le ramasse-miettes 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 -gc par le ramasse-miettes si la référence forte à l'objet Remote est publiée . Un -nogc est observé lorsque le client se connecte après la récupération de l'objet -hold.
  • -release Ne demandez pas explicitement un garbage collection. Cela rendra probablement l'objet -delay<S> accessible au client, qu'une référence forte soit maintenue ou libérée sauf s'il existe un délai suffisant entre le démarrage du serveur et l'appel du client tel que que le système & "naturellement &"; appelle le ramasse-miettes et récupère le -delay5 objet .
  • System.gc() Conservez une référence forte à l'objet javac RMITest.java. Dans ce cas, une variable de classe fait référence à l'objet <=>.
  • <=> (par défaut) Une référence forte à l'objet <=> sera publiée. Dans ce cas, une variable de méthode fait référence à l'objet <=>. Une fois la méthode renvoyée, la référence forte est perdue.
  • <=> Nombre de secondes à attendre entre le démarrage du serveur et l'appel du client. L'insertion d'un délai donne au ramasse-miettes le temps nécessaire pour exécuter & «Naturellement». & «; Ceci 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: <=> appelle le 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 <=> ne sont que des astuces et la définition de l'option <=> est un jeu de devinettes en ce qui concerne le comportement du garbage collector.

Sur ma machine, après <=> la compilation, 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);
    }
}

Autres conseils

Quelques autres questions à prendre en compte - Commencez-vous par référencer une instance d'objet ou l'interface de stub elle-même 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 de votre 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

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 protège " serviceImpl " objet d'être GC'd. CMIIW

il manque un point dans la discussion ci-dessus. Il y a quelque chose qui s'appelle la collecte de déchets distribuée (DGC). S'il n'y a pas de références vivantes locales et distantes à un objet distribué, le CPG est autorisé à supprimer l'objet de la mémoire. Il existe un algorithme sophistiqué pour vérifier cela. Le bel extrait de code d’en haut est en effet une bonne démonstration de l’efficacité de la DGC.

Ce qui ressemble à une fonctionnalité n’est rien d’autre que le comportement conçu!

Frank

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

  • Les objets serveur doivent être désenregistrés d'une manière ou d'une autre
  • Les points d'arrêt éliminant les erreurs, il s'agit certainement d'une situation de concurrence critique.

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

Lors de l’utilisation de la télécommande de ressort (rmi), j’ai rencontré cette erreur. Mon service n'a pas été collecté.

Après avoir activé la journalisation du 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 " a montré une sortie sur le serveur lorsque le client essayait de se connecter.

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

Vous avez la même erreur, mais probablement pour une autre raison encore inconnue.

Je convertissais l'objet exporté selon le type de mon interface distante, puis, tout en me liant à nommer, j'obtenais NoSuchObjectException. Supprimer le casting a résolu le problème.

brièvement:

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);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top