I'm writing a Java client-server application that uses RMI for communication. My problem is that for some reason, the RMI server just shuts down with no exception or error, on its own. I'm using Netbeans and I ran a profile to look at the threads.
You can see in the attached image the point in time where the application supposedly finished executing as the end of the GC Daemon and the RMI Reaper threads. However, even after the application ended, the RMI TCP Accept-1099 thread is still running. The part that confuses me even more is that after the Information message popped up (you can see it in the screenshot) telling me that the server has stopped, the threads continue to be updated in the diagram so I tried to connect with the client again. Although it failed, I can see a new RMI thread being created (connection 18).
I have no idea how to debug this issue, and I can't figure out how it is possible for the application to exit when the RMI accept thread is still running.
Update: Here is the server's main method:
/**
* Main entry point.
*
* @param args the application arguments - unused.
*/
public static void main(String[] args) {
try {
System.setProperty("java.rmi.dgc.leaseValue", "30000");
sServerProperties = new ServerProperties();
System.setProperty("java.rmi.server.hostname", sServerProperties.
getRmiServer());
createRmiRegistry();
ConfigCore configCore = new ConfigCore();
ServerCore server = new ServerCore(configCore);
LoginHandler loginHandler = new LoginHandler(server);
sRegistry.
bind(Login.class.getSimpleName(), loginHandler.getRemote());
Logger.log(Level.INFO, "Server ready!");
} catch (RemoteException ex) {
Logger.log(Level.SEVERE, "Unable to start RMI registry", ex);
} catch (SQLException ex) {
Logger.log(Level.SEVERE, "Unable to connect to the MySQL server",
ex);
System.err.println(ex.getMessage());
} catch (IOException ex) {
Logger.log(Level.SEVERE, "Unable to load or write properties file",
ex);
System.err.println(ex.getMessage());
} catch (AlreadyBoundException ex) {
Logger.log(Level.SEVERE, "RMI port already bounded", ex);
} catch (NoSuchAlgorithmException ex) {
Logger.log(Level.SEVERE, "Unable to digest password", ex);
}
}
/**
* Creates the RMI registry.
*
* @throws RemoteException if the RMI registry could not be created.
*/
private static void createRmiRegistry() throws RemoteException {
if (sRegistry == null) {
Logger.log(Level.INFO, "Creating RMI Registry...");
sRegistry = LocateRegistry.createRegistry(sServerProperties.
getRmiPort());
}
}
You're seeing an interaction of the VM exiting when its last non-daemon thread exits, combined with HotSpot garbage collection behavior, RMI's exporting behavior, and running the JVM under the NetBeans profiler.
The main thread exits after the main()
method returns. However, you've exported an RMI server object, so RMI keeps the JVM alive by running the "RMI Reaper" thread (non-daemon) as long as there are live, exported objects. RMI determines whether an exported object is "alive" by keeping only a weak reference to it in it object table.
Unfortunately, from looking at the main()
method, it appears that your RMI server object is only referenced via local variables. Thus, it will get garbage collected sooner or later, but for the weak reference to it in RMI's object table. When the object becomes weakly reachable, the RMI Reaper unexports it and exits. Since the RMI Reaper is the last non-daemon thread, the JVM exits.
Note that the RMI registry is treated specially. Exporting a registry will not keep a JVM alive.
Note also that putting an infinite loop at the end of the main()
method will not necessarily prevent the RMI server object from being unexported and GC'd. The reason is that objects are subject to GC when they become unreachable, and a reference being present in a local variable of an active method is not sufficient to make it reachable. See my answer to another question on that topic. Of course, putting an infinite loop into main()
will prevent the JVM from exiting, since it keeps the main thread alive, and the main thread is not a daemon thread.
To prevent your RMI server from being unexported, it's usually sufficient to store a reference to it in a static field.
Now, why does the JVM stick around when run under the profiler? That's just an artifact of the way the profiler works. The profiler detects that the last non-daemon thread has exited (other than additional threads running in the JVM on behalf of the profiler), so that's when it pops up the dialog that says "The profiled application has finished execution." It keeps the JVM alive, though, so you can continue to get data from it; this has the side effect keeping all the daemon threads alive. You've exported a registry, so that keeps listening on port 1099. When the JVM is in this state you might still be able to register RMI objects if you tried. Your RMI server object has long since been unexported and GC'd, so requests to it won't work. But that's why the RMI connection was still accepted when the JVM was in this state.
Bottom line is, make sure your RMI server objects don't get GC'd. A good way to do this is to make sure they're reachable from a static field.
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