RMI Threads prevent JVM from exiting after main() completes
Asked Answered
I

2

10

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?

Infidelity answered 21/2, 2012 at 23:57 Comment(5)
Is that test program supposed to hang? What JRE are you using? Have you tried >= 1.6? Are the "reaper" and "connection-accept" really the only non-daemon threads left?Debacle
Yes - those two threads are the only ones left when I profile the application. The test program is supposed to continue running after main completes, when you comment out the call to UnicastRemoteObject.unexportObject(obj,true). The desired behavior is for the test program to exit. I suspect I am probably missing a call to unexportObject somewhere, but was just curious if there are any other ways to leave RMI threads running.Infidelity
And yes, I am running both Java 1.6 and 1.7 - the behavior is the same in both versions.Infidelity
One thing to consider is to only do the bind() operations through some internal API so you can track which objects you have yet to un-bind. Never call the registry directly except through the manager singleton. Keep a map of them or something (maybe IdentityHashMap) and remove them when you unexportObject.Debacle
Yeah, that's a good idea. I had added a "close()" method to all of my classes that extended UnicastRemoteObject and was relying calls to close() propagating up the hierarchy to unexport all of them - that's where I ran into trouble, because one call to close() wasn't being made. Good suggestion!Infidelity
I
8

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.

Infidelity answered 22/2, 2012 at 0:19 Comment(0)
D
7

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));
        }
    }
}
Debacle answered 22/2, 2012 at 0:46 Comment(4)
You don't need the local Map. The Registry is already a map. You can implement this entire class without registeredMap.Fade
You are missing the point @EJP. The map shows what things have been registered by the application but not unregistered.Debacle
Oh, I see. There is a list[] method. I'll have to see if there it also show internal objects. I'll edit my answer.Debacle
I've altered the code to use the registry table but I think there is still a need for the class since the UnicastRemoteObject objects are a subset of the registered onces. ThanksDebacle

© 2022 - 2024 — McMap. All rights reserved.