java.rmi.NoSuchObjectException: nessun oggetto del genere nella tabella
Domanda
Sto scrivendo un server RMI molto semplice e vedo intermittenti java.rmi.NoSuchObjectExceptions
nei test unitari.
Ho una serie di chiamate a metodi remoti sullo stesso oggetto e mentre i primi passano, a volte quelli successivi falliscono. Non sto facendo nulla per annullare la registrazione dell'oggetto server tra.
Questi errori non compaiono sempre e se inserisco dei punti di interruzione tendono a non apparire. Quei Heisenbugs, le cui condizioni di razza si dissolvono quando li guardano attraverso l'esecuzione rallentata del debugger? Non c'è multi-threading in corso nel mio codice di test o server (anche se forse all'interno dello stack RMI?).
Lo sto eseguendo su Mac OS X 10.5 (Java 1.5) tramite il plugin JUnit di Eclipse, e il server e il client RMI sono entrambi nella stessa JVM.
Cosa può causare queste eccezioni?
Soluzione
Mantieni un forte riferimento all'oggetto che implementa l'interfaccia java.rmi.Remote
in modo che rimanga raggiungibile , ovvero non idoneo per la raccolta dei rifiuti.
Di seguito è riportato un breve programma che mostra un <= > . Lo script è autonomo, creando un registro RMI e un & Quot; client & Quot; e un " server " in un'unica JVM.
Basta copiare questo codice e salvarlo in un file chiamato java.rmi.NoSuchObjectException
. Compila e invoca con la tua scelta di argomenti da riga di comando:
RMITest.java
(impostazione predefinita) Indica esplicitamente alla JVM di fare " uno sforzo migliore " per eseguire il Garbage Collector dopo l'avvio del server, ma prima che il client si connetta al server. Ciò probabilmente causerà il recupero dell'oggetto-gc
da parte del garbage collector se il riferimento forte all'oggettoRemote
è rilasciato . Un-nogc
viene osservato quando il client si connette dopo che-hold
l'oggetto è stato recuperato.-release
Non richiedere esplicitamente la raccolta dei rifiuti. Ciò probabilmente farà sì che l'oggetto-delay<S>
rimanga accessibile dal client indipendentemente dal fatto che venga mantenuto o rilasciato un riferimento sicuro a meno che non ci sia un ritardo sufficiente tra l'avvio del server e la chiamata del client che il sistema " naturalmente " invoca il Garbage Collector e recupera-delay5
l'oggetto .System.gc()
Mantieni un riferimento forte all'oggettojavac RMITest.java
. In questo caso, una variabile di classe fa riferimento all'oggetto <=>.- <=> (impostazione predefinita) Verrà rilasciato un riferimento forte all'oggetto <=>. In questo caso, una variabile di metodo fa riferimento all'oggetto <=>. Dopo il ritorno del metodo, il riferimento forte viene perso.
- <=> Il numero di secondi di attesa tra l'avvio del server e la chiamata del client. L'inserimento di un ritardo fornisce tempo al garbage collector per eseguire & Quot; naturalmente. & Quot; Questo simula un processo che & Quot; funziona & Quot; inizialmente, ma fallisce dopo che è trascorso un po 'di tempo significativo. Nota: non c'è spazio prima del numero di secondi. Esempio: <=> chiamerà il client 5 secondi dopo l'avvio del server.
Il comportamento del programma varierà probabilmente da macchina a macchina e da JVM a JVM perché cose come <=> sono solo suggerimenti e l'impostazione dell'opzione <=> è un gioco d'ipotesi rispetto al comportamento del garbage collector.
Sul mio computer, dopo <=> la compilazione, vedo questo comportamento:
$ 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)
Ecco il codice sorgente:
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);
}
}
Altri suggerimenti
Alcune altre domande da considerare: per prima cosa stai facendo riferimento a un'istanza di un oggetto o l'interfaccia stessa dello stub è sparita? Se qualche istanza di oggetto è sparita, è per i soliti motivi, è stata sottoposta a dereferenziazione e GC'd, ma se è l'interfaccia, il loop del punto finale del server RMI si è chiuso per qualche motivo.
Il miglior strumento di debug che ho trovato finora è attivare la proprietà java.rmi.server.logCalls = true (vedi http://java.sun.com/j2se/1.5.0/docs/guide/rmi/javarmiproperties.html ) e guarda tutte le informazioni meravigliose in streaming nella finestra del registro. Questo mi dice che succede ogni volta.
Jos
Ho lo stesso problema e ora l'ho risolto. La soluzione è semplice, DEVI creare un "oggetto" di riferimento forte per evitare che l'oggetto sia GC'd.
ad esempio nella tua classe di server:
...
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...
}
Quindi, protegge " serviceImpl " oggetto da essere GC'd. CMIIW
C'è un punto mancante nella discussione sopra. C'è qualcosa che si chiama Garbage Collection distribuito (DGC). Se non ci sono riferimenti locali e remoti viventi a un oggetto distribuito, al GC è consentito rimuovere l'oggetto dalla memoria. Esiste un algoritmo sofisticato per verificarlo. Il bel frammento di codice dall'alto è davvero una buona dimostrazione dell'efficacia della DGC.
Quello che in qualche modo sembra una caratteristica non è altro che il comportamento progettato!
Frank
È difficile rispondere a questa domanda senza guardare il codice (che immagino sarà abbastanza grande da non essere pubblicabile qui). Tuttavia, usando il rasoio di Occam, hai due possibilità
- Gli oggetti server devono essere in qualche modo non registrati
- Poiché i punti di interruzione bloccano gli errori, è sicuramente una condizione di gara.
Suggerirei di esaminare attentamente i percorsi del codice tenendo presente i due punti precedenti.
Durante l'utilizzo del telecomando a molla (rmi) ho riscontrato questo errore. Il mio servizio non è stato raccolto spazzatura.
Dopo aver attivato la registrazione debug per " org.springframework " ho scoperto che il mio server stava registrando il servizio sulla porta predefinita (1099) anziché sulla porta a cui il client stava tentando di connettersi.
Ho pensato che tutto il punto di vista della porta fosse ok causa " java.rmi.server.logCalls = true " ha mostrato un output sul server quando il client stava tentando di connettersi.
Quando si ottiene questo errore, ricontrollare le porte (quella del servizio e quella del registro).
Ha ottenuto lo stesso errore ma probabilmente per l'altro motivo (ancora sconosciuto).
Stavo lanciando l'oggetto esportato sul tipo della mia interfaccia remota e poi, mentre mi collegavo al nome, ottenevo NoSuchObjectException. La rimozione del casting ha risolto il problema.
In breve:
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);
}