Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RMI Threads prevent JVM from exiting after main() completes

To make a long story short, I'm having trouble getting a couple of Java RMI's non-daemon threads to close out after my application no longer needs RMI. This prevents the JVM from exiting when main() completes.

I understand that exporting UnicastRemoteObjects will cause RMI to leave threads open until you successfully call UnicastRemoteObject.unexportObject(Object o,boolean force). Here's an example (run without modification and the JVM will exit normally - remove the call to unexportObject and the JVM will never exit):

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class TestUnicastRemoteObject{
private static UnicastRemoteObject obj;
private static Registry registry;

public static void main(String[] args) throws Exception{
    obj = new UnicastRemoteObject(){
        private static final long serialVersionUID = 1L;
    };
    System.err.println("created UnicastRemoteObject");
    System.err.println("creating registry ...");
    registry = LocateRegistry.createRegistry(9999);
    System.err.println("registry created.");
    System.err.println("binding obj to registry ...");
    registry.bind("Test", obj);
    System.err.println("bound");
    UnicastRemoteObject.unexportObject(obj, true);
    System.err.println("unexported obj");
}
}

Also, it doesn't seem to matter whether you create the registry and/or bind the remote object to it - the only thing that seems to matter in this example is that any time you create a UnicastRemoteObject, you need to call unexportObject in order to prevent any threads from remaining after you're done.

In my application, I've made sure that I've called unexportObject on every UnicastRemoteObject I create, and yet RMI's "reaper" thread and "connection accept" thread still persist, preventing my JVM from exiting when my application is finished using RMI resources.

Is there something else that could cause RMI to leave threads behind, aside from forgetting to unexport UnicastRemoteObjects?

like image 885
CodeBlind Avatar asked Feb 21 '12 23:02

CodeBlind


People also ask

Is Java RMI multithreaded?

An RMI server is inherently multi-threaded.

Is RMI thread safe?

The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads. Since remote method invocation on the same remote object may execute concurrently, a remote object implementation needs to make sure its implementation is thread-safe.

How does Java RMI work?

RMI passes objects by their actual classes, so the behavior of the objects is not changed when they are sent to another Java virtual machine. This capability enables new types and behaviors to be introduced into a remote Java virtual machine, thus dynamically extending the behavior of an application.


2 Answers

Sure enough, I had a bug in the code that caused one of my (many) UnicastRemoteObjects to not unexport itself when the calling application was done utilizing it. So the answer is:

Unexporting all UnicastRemoteObjects within a running JVM is sufficient to close all RMI non-daemon threads.

like image 87
CodeBlind Avatar answered Sep 22 '22 09:09

CodeBlind


Sounds like you solved you problem @Ben but for posterity, I thought I'd promote my comment to an answer. Whenever I have a register/unregister type of pattern I make sure to manage them through a singleton object. This way you have one place to go to figure out which object was not unregistered. Exposing this in JMX is also a win.

Something like the following code would be good. It will allow you to log or JMX query to see what objects have been bound to the registry but have yet to be unbound.

public class UnicastRegistry {
    private static Registry registry;
    private static UnicastRegistry singleton;
    // private to force the singleton
    private UnicastRegistry() throws RemoteException {
        registry = LocateRegistry.createRegistry(9977);
    }
    public static UnicastRegistry createSingleton() throws RemoteException {
        if (singleton == null) {
            singleton = new UnicastRegistry();
        }
        return singleton;
    }
    public void register(String label, Remote obj) throws Exception {
        registry.bind(label, obj);
    }
    public void unregister(String label) throws Exception {
        Remote remote = registry.lookup(label);
        registry.unbind(label);
        if (remote instanceof UnicastRemoteObject) {
            UnicastRemoteObject.unexportObject(remote, true);
        }
    }
    public void unregisterAll() throws Exception {
        for (String label : registry.list()) {
            unregister(label);
        }
    }
    public void printStillBound() throws Exception {
        String[] stillBound = registry.list();
        if (stillBound.length > 0) {
            System.out.println("Still bound = " + Arrays.toString(stillBound));
        }
    }
}
like image 25
Gray Avatar answered Sep 21 '22 09:09

Gray