minus
minus

Reputation: 2786

RMI garbage collection of callbacks

I need to create a RMI service which can notify events to clients. Each client register itself on the server, the client can emit an event and the server will broadcast it to all other clients.

The program works, but, the client reference on the server is never garbage collected, an the thread which the server uses to check if the client reference will never terminate.

So each time a client connects to the server, a new thread is created and never terminated.

The Notifier class can register and unregister a listener.

The broadcast method call each registered listener and send the message back.


public class Notifier extends UnicastRemoteObject implements INotifier{

    private List<IListener> listeners = Collections.synchronizedList(new ArrayList());
    public Notifier() throws RemoteException {
        super();
    }
    
    @Override
    public void register(IListener listener) throws RemoteException{
        listeners.add(listener);
    }
    
    @Override
    public void unregister(IListener listener) throws RemoteException{
        boolean remove = listeners.remove(listener);
        if(remove) {
            System.out.println(listener+" removed");
        } else {
            System.out.println(listener+" NOT removed");
        }
    }
    
    @Override
    public void broadcast(String msg) throws RemoteException {
        for (IListener listener : listeners) {
            try {
                listener.onMessage(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

The listener is just printing each received message.

public class ListenerImpl extends UnicastRemoteObject implements IListener {

    public ListenerImpl() throws RemoteException {
        super();
    }

    @Override
    public void onMessage(String msg) throws RemoteException{
        System.out.println("Received: "+msg);
    }

}

The RunListener client subscribes a listener wait few seconds to receive a message and then terminates.

public class RunListener {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry();
        INotifier notifier = (INotifier) registry.lookup("Notifier");
        ListenerImpl listener = new ListenerImpl();
        notifier.register(listener);
        Thread.sleep(6000);
        notifier.unregister(listener);
        UnicastRemoteObject.unexportObject(listener, true);

    }
}

The RunNotifier just publish the service and periodically sends a message.



public class RunNotifier {
    static AtomicInteger counter = new AtomicInteger();

    public static void main(String[] args) throws RemoteException, AlreadyBoundException, NotBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        INotifier notifier = new Notifier();
        registry.bind("Notifier", notifier);

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    int n = counter.incrementAndGet();
                    System.out.println("Broadcasting "+n);
                    notifier.broadcast("Hello ("+n+ ")");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        },5 , 5, TimeUnit.SECONDS);
        
        try {
            System.in.read();
        } catch (IOException e) {
        }
        executor.shutdown();
        registry.unbind("Notifier");
        UnicastRemoteObject.unexportObject(notifier, true);
    }

}

I've seen many Q&A on stack overflow about RMI, but none addresses this kind of problem.

I guess I'm doing some very big mistake, but I can't spot it.

The threads running on the server instance

As you can see in the picture, a new RMI RenewClean thread is created for each incoming connection, and this thread will never terminate.

Once the client disconnects, and terminates, the RenewClean thread will silently swallow all ConnectionException thrown and will keep polling a client which will never reply.

As a side note, I even tried to keep just weak reference of the IListener in the Notifier class, and still the results are the same.

Upvotes: 0

Views: 626

Answers (1)

DuncG
DuncG

Reputation: 15186

This may not be very helpful if you are stuck on JDK1.8, but when I test on JDK17 the multiple rmi server threads created for each incoming client RMI RenewClean-[IPADDRESS:PORT] are cleaned up on the server, and not showing "will never terminate" behaviour you may have observed on JDK1.8. It may be a JDK1.8 issue, or simply that you are not waiting long enough for the threads to end.

For quicker cleanup, try adjusting the system property for client thread garbage collection setting from the default (3600000 = 1 hour):

java -Dsun.rmi.dgc.client.gcInterval=3600000 ...

On my server I added this in one of the API callbacks:

Function<Thread,String> toString = t -> t.getName()+(t.isDaemon() ? " DAEMON" :"");

Set<Thread> threads = Thread.getAllStackTraces().keySet();
System.out.println("-".repeat(40)+" Threads x "+threads.size());
threads.stream().map(toString).forEach(System.out::println);

After RMI server startup it printed names of threads and no instances of "RMI RenewClean":

---------------------------------------- Threads x 12

After connecting many times from a client, the server reported corresponding instances of "RMI RenewClean":

---------------------------------------- Threads x 81

Leaving the RMI server for a while, these gradually shrank back - not to 12 threads -, but low enough to suggest that RMI thread handling is not filling up with many unnecessary daemon threads:

---------------------------------------- Threads x 20

After about an hour all the remaining "RMI RenewClean" were removed - probably due to housekeeping performed at the interval defined by the VM setting sun.rmi.dgc.client.gcInterval=3600000:

---------------------------------------- Threads x 13

Note also that RMI server shutdown is instant at any point - the "RMI RenewClean" daemon threads do not hold up rmi server shutdown.

Upvotes: 1

Related Questions