9

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. Server 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());
    }
}
rhobincu
  • 906
  • 1
  • 7
  • 22
  • Is your application in an infinite loop? How do you ensure that your `main` thread doesn't finish? – Boris the Spider Sep 16 '14 at 15:46
  • The main thread finishes immediately after exporting and binding a RMI stub. The application should only finish after all threads are closed, not just the main thread. The main thread is the first bar in the thread graph, you can see it close immediately. – rhobincu Sep 16 '14 at 15:47
  • All _non daemon_ threads. – Boris the Spider Sep 16 '14 at 15:47
  • Ok, then why isn't the application shutting down immediately after main finishes? – rhobincu Sep 16 '14 at 15:48
  • It looks as though the profiler is mistaking a return from main() as an application exit. There's no other evidence that the application is shutting down, and if you run it outside the profiler it certainly won't shut down while the accept thread is running. – user207421 Sep 16 '14 at 21:16
  • @EJP unfortunately, it does stop outside of the profiler as well, that's the original problem. It works for a while, then all of a sudden, it just stops. Also, the profiler displays the "finished execution" message long after the main thread exits. – rhobincu Sep 16 '14 at 21:22
  • @BoristheSpider Quick question: is the RMI TCP Accept{0} a daemon thread? I couldn't find a clear answer. – rhobincu Sep 16 '14 at 21:27
  • No it isn't a daemon thread. I have production code that relies on the fact. – user207421 Sep 16 '14 at 21:38
  • 1
    @EJP In JDK 8 at least, the RMI accept threads are daemon threads. This might have been different in the past, but they've been daemon threads for a long time. – Stuart Marks Sep 16 '14 at 23:05

2 Answers2

9

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.

Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • Hang on. (1) The Registry is not treated specially. If you don't store it statically it can get GCd just like anything else. (2) The OP's remote object is bound in the Registry, which causes DGC leasing, which causes a strong local reference, which prevents local GC, as long as the Registry itself remains uncollected. – user207421 Sep 16 '14 at 23:14
  • 1
    @EJP (1) The registry is treated specially in that exporting one won't keep a JVM alive. You are correct that if you don't store it statically, it can be GC'd. Even though OP is storing the registry statically, the JVM is still exiting. (2) Typically binding a stub into a registry via its remote stub will involve DGC leasing as you say. Note carefully though the OP is binding the stub into the registry **implementation** (`sRegistry`) which does not involve DGC nor any remote calls. – Stuart Marks Sep 16 '14 at 23:34
  • Please clarify, when you talk about the RMI server, you are referring to the Login object? I take it exporting it using the static sRegistry instance isn't enough to keep it alive? – rhobincu Sep 17 '14 at 08:40
  • Also, can you explain what you mean by "OP is binding the stub into the registry implementation" is there another way of binding a stub? – rhobincu Sep 17 '14 at 08:41
  • 1
    @rhobincu The RMI server object is the one that either a) extends `UnicastRemoteObject` or b) implements `Remote` and on which `UnicastRemoteObject.exportObject()` has been called. I can't tell which it is from the code you've posted, but it could be the `LoginHandler` object or be contained within it. I'm not entirely sure, but I'd guess that `LoginHandler.getRemote()` returns the **stub** of the server object. Crucially, the stub does **not** contain an object reference to the implementation object and thus won't prevent it from being GC'd. – Stuart Marks Sep 17 '14 at 15:04
  • 1
    @rhobincu "OP is binding the stub into the registry implementation" means that the OP (original poster, i.e., you, or more precisely, the code you posted) has taken what I assume to be the stub and bound it into the registry. But `LocateRegistry.createRegistry()` returns a registry **implementation** whereas `LocateRegistry.getRegistry()` returns the **stub** of the registry. Binding directly using the implementation isn't a problem and is more efficient than using the stub, since it avoids issuing a remote call to an object that's in the same JVM. – Stuart Marks Sep 17 '14 at 15:08
  • 1
    @rhobincu But binding using the registry stub would pass the server stub through a remote call, which would cause DGC (distributed garbage collection) to hold additional references to your server object. This in turn would have the side effect of keeping the server object alive (preventing GC). This is a bit magical though. It's probably simpler to just keep a reference to the server object **implementation** (not just the stub) in static data somewhere. – Stuart Marks Sep 17 '14 at 15:12
  • @StuartMarks Thanks! It makes sense! I'm up-voting for the extensive explanations, but I will first verify before marking as accepted. :) – rhobincu Sep 17 '14 at 15:22
-2

This is a common problem with RMI. You need to save the reference to your implementation as a class field so it doesn't get GC. And you need to keep that class (with the main()) alive as well.

I use this as a never-ending keep alive:

for(;;) LockSupport.park(); 

You can do anything you like so the implementation remains alive and the class with the main() stays alive.

edharned
  • 1,884
  • 1
  • 19
  • 20
  • 1
    Neither of these statement is true. He need to save the *Registry* object as a static object! to prevent it from GC, and it appears that he is doing that. – user207421 Sep 16 '14 at 21:14
  • I am saving all the exported references. I wasn't aware I need to keep main running. In fact, I used to do a Main.class.wait() after binding the login stub, it still didn't help and the server kept shutting down randomly. – rhobincu Sep 16 '14 at 21:23
  • @EJP I haven't been doing RMI as long as you, but 16 years is close. As always, it is nice to talk to you again. I used to have the same problem and what I do, above, works to keep the Server alive. I do keep a reference to the stub, but that was a no-brainier. OP wait() may have a spurious wake up, so use the park(). If that doesn't solve your problem, then you have another issue. – edharned Sep 16 '14 at 22:09
  • It *works* to keep the server alive, but it isn't *necessary* to keep the server alive. He doesn't 'need' to do this. – user207421 Sep 16 '14 at 23:16