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?

È stato utile?

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'oggetto Remote è 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'oggetto javac 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);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top