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 UnicastRemoteObject
s 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?
An RMI server is inherently multi-threaded.
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.
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.
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.
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));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With