Java.rmi.NoSuchObjectException: aucun objet de ce type dans la table
J'écris un serveur RMI très simple, et je vois intermittent java.rmi.NoSuchObjectExceptions
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 échoueront parfois. Je ne fais rien pour annuler l'enregistrement de l'objet serveur entre les deux.
Ces erreurs n'apparaissent pas toujours, et si je mets des points d'arrêt, ils ont tendance à ne pas apparaître. Sont ces Heisenbugs, dont les conditions de course se dissolvent en les regardant à travers le ralenti l'exécution du débogueur? Il n'y a pas de multi-threading dans mon code de test ou de serveur (bien que peut-être à l'intérieur de la pile RMI?).
Je l'exécute sur Mac OS X 10.5 (Java 1.5) via le plugin JUnit D'Eclipse, et le serveur RMI et le client sont tous deux dans la même JVM.
Qu'est-ce qui peut causer ces exceptions?
6 réponses
Gardez une référence forte à l'objet qui implémente l'interface java.rmi.Remote
afin qu'il reste accessible, c'est-à-dire inéligible pour la récupération de place.
Ci-Dessous est un programme court qui montre un java.rmi.NoSuchObjectException
. Le script est autonome, créant un registre RMI ainsi qu'un "client" et un "serveur" dans une seule JVM.
Copiez simplement ce code et enregistrez - le dans un fichier nommé RMITest.java
. Compiler et invoquer avec votre choix de ligne de commande arguments:
-
-gc
(default) demande explicitement à la JVM de faire "le meilleur effort" 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'objetRemote
par le garbage collector si la référence forte à l'objetRemote
est libérée. Unjava.rmi.NoSuchObjectException
est observé lorsque le client se connecte après la récupération de l'objetRemote
. -
-nogc
Ne pas demander explicitement la récupération de place. Cela entraînera probablement l'objetRemote
à rester accessible par le client, qu'une référence forte soit conservée ou libérée , à moins qu'il n'y ait un délai suffisant entre le démarrage du serveur et l'appel du client, de sorte que le système appelle "naturellement" le garbage collector et récupère l'objetRemote
. -
-hold
Conserver une référence forte à laRemote
objet. Dans ce cas, une variable de classe fait référence àRemote
objet. -
-release
(par défaut) Une référence forte à laRemote
objet sera libéré. Dans ce cas, une variable de méthode fait référence à l'objetRemote
. Après le retour de la méthode, la référence forte est perdue. -
-delay<S>
le nombre de secondes à attendre entre le démarrage du serveur et l'appel du client. L'insertion d'un délai permet au garbage collector de s'exécuter "naturellement"."Cela simule un processus qui "fonctionne" initialement, mais échoue après un certain temps significatif a passé. Remarque il n'y a pas d'espace avant le nombre de secondes. Exemple:-delay5
fera l'appel du client 5 secondes après le démarrage du serveur.
Le comportement du programme variera probablement d'une machine à L'autre et D'une JVM à une JVM, car des choses comme System.gc()
ne sont que des indices et la définition de l'option -delay<S>
est un jeu de devinettes par rapport au comportement du garbage collector.
Sur ma machine, après javac RMITest.java
pour compiler, je vois 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 la source code:
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);
}
}
Quelques autres questions à considérer-d'abord référencez-vous une instance d'objet ou l'interface stub elle-même a-t-elle disparu? Si une instance d'objet a disparu, c'est pour les raisons habituelles, elle a été déréférencée et GC'D, mais si c'est l'interface, votre boucle de point final de serveur RMI a quitté pour une raison quelconque.
Le meilleur outil de débogage que j'ai trouvé jusqu'à présent est d'activer java.rmi.serveur.logCalls = true propriété (Voir http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html ) et regardez tous les flux d'informations merveilleuses dans votre fenêtre de journal. Ça 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 une référence forte 'object' pour éviter que L'objet ne soit GC'D.
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...
}
Donc, il protège l'objet" serviceImpl " d'être GC'D. CMIIW
Il manque un point dans la discussion ci-dessus. Il y a quelque chose qui s'appelle distributed garbage collection (DGC). S'il n'y a pas de références locales et distantes vivantes à un objet distribué, le GC est autorisé à supprimer l'objet de la mémoire. Il existe un algorithme sophistiqué pour vérifier cela. L'extrait de code de nice ci-dessus est en effet une bonne démonstration de l'efficacité du DGC.
Ce qui ressemble en quelque sorte à une fonctionnalité n'est rien d'autre que le comportement conçu!
Frank
Il est difficile de répondre à cette question sans regarder le code (qui, je suppose, sera assez grand pour ne pas être publiable ici). Cependant, en utilisant le rasoir D'Occam, vous avez deux possibilités
- les objets serveur doivent être non enregistrés d'une manière ou d'une autre
- puisque les points d'arrêt arrêtent les erreurs, c'est définitivement une condition de course.
Je vous suggère de parcourir les chemins de code en gardant soigneusement à l'esprit les deux points ci-dessus.
A eu la même erreur mais probablement pour l'autre raison (encore inconnue).
Je lançais l'objet exporté vers le type de mon interface distante, puis pendant la liaison au nom, je recevais NoSuchObjectException. Enlever la coulée 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);
}