Under certain circumstances, the Java RMI mechanism can prevent the graceful shutdown of the JVM. This article explains the reasons for this behavior and presents some solutions.
As found in OpenJDK source code, the class
PermanentExporter, has the following comment:
|Prevents our RMI server objects from keeping the JVM alive. We use a private interface in Sun’s JMX Remote API implementation that allows us to specify how to export RMI objects. We do so using UnicastServerRef, a class in Sun’s RMI implementation. This is all non-portable, of course, so this is only valid because we are inside Sun’s JRE.|
The following article explains -
- How the RMI mechanism can prevent a JVM from shutting down gracefully?
- What are the possible solutions to this problem?
- How the private interface in Sun’s implementation overcomes this problem?
How can RMI prevent a JVM from shutting down?
As per the documentation in the class
|The Java virtual machine shuts down in response to the following events:
So, in the absence of a call to
System.exit) or a user interrupt or a system-wide event, the only thing which can prevent a JVM from shutting down gracefully is a running non-daemon thread.
This means that the RMI mechanism must be spawning a non-daemon thread, which if running, prevents the JVM from shutting down gracefully.
Which non-daemon thread does RMI spawn?
To find this, I developed two variants of a program – one which does not export RMI remote objects and one which does.
Non-daemon threads in the process which does not export RMI remote objects were:
- VM Thread
- VM Periodic Task Thread
Non-daemon threads in the process which exports RMI remote objects were:
- VM Thread
- VM Periodic Task Thread
- RMI Reaper
This means that RMI Reaper should be the non-daemon thread which may prevent the graceful shut down of the JVM.
What is the function of the RMI Reaper thread?
Following diagram explains the usual life cycle of a RMI remote object -
The RMI Reaper thread is started by the RMI mechanism when the first non-permanent RMI remote object is exported. [Note: the meaning of “permanent” objects is explained further below]
When a RMI remote object is exported, the RMI mechanism stores a weak reference to this object in the server JVM; and this weak reference is registered to a reference queue so that the garbage collector sends a notification to this reference queue when the RMI remote object is garbage collected.
The RMI remote object is garbage collected when it has no strong references in the server JVM.
The RMI Reaper thread polls the weak reference queue for notifications. When it receives such a notification, the RMI Reaper thread performs internal cleanup.
The RMI Reaper thread is interrupted when all the non-permanent RMI server objects are dead. A RMI server object is dead if it is not servicing any RMI call and it is garbage collected.
Because the RMI Reaper thread is a non-daemon thread, the JVM will not shut down gracefully until the RMI Reaper thread is running. Thus, the RMI Reaper thread also serves the purpose of keeping the JVM alive until all non-permanent RMI server objects are dead.
What should be done to ensure a graceful JVM shut down?
The RMI Reaper thread would not be running if –
- No RMI remote object was ever exported in the server JVM or,
- All the below conditions are true for every RMI remote object -
- The server side does not hold a strong reference, other than the one held by the RMI mechanism, to the RMI server object, and
- The client does not hold any references to the RMI remote object or the lease period of the RMI remote object has expired or the RMI remote object is un-exported, and
- The clients voluntarily stop making any remote calls on the RMI remote object or the RMI remote object is un-exported
Thus, to ensure a graceful JVM shut down, following needs to be done:
- Remove strong references to the RMI remote objects from the server JVM
- Un-export all the exported RMI remote objects – this will stop the server from accepting any further remote calls and remove any strong references held by the RMI mechanism
Note: RMI mechanism allows two variants of the un-export API:
- Variant 1 does not un-export the RMI server object if there are any pending or in-progress calls on it.
- Variant 2 forcibly un-exports the RMI server object even if there are any pending or in-progress calls on it.
Can shutdown hooks be used to un-export RMI remote objects?
Shutdown hooks are invoked when the JVM begins its shutdown sequence. Except for
System.exit) or a user interrupt or a system-wide shutdown event, the JVM will not begin its shutdown sequence until the last non-daemon thread exits. So, the shutdown hooks will not be invoked until the RMI Reaper thread is running. Therefore, shutdown hooks should not be used to un-export non-permanent RMI remote objects.
How does the private interface in Sun’s RMI implementation solve this problem?
Sun’s implementation provides an out-of-box management agent. This agent may start a JMX connector server on a specific port – this involves exporting RMI remote objects in the agent’s JVM. To prevent this agent from stopping a graceful JVM shutting down, Sun’s RMI implementation introduces the concept of “permanent” RMI remote objects.
A permanent RMI remote object always has a strong reference held by the RMI mechanism, so that it is not garbage collected. So, the RMI Reaper thread does not receive any notification for a permanent RMI remote object. Also, the RMI Reaper can be interrupted even if there are any live permanent RMI remote objects.
Thus, the JVM shut down will complete gracefully even if there are any live permanent RMI remote objects.
Can we use Sun’s private interface to get rid of this problem?
We can use Sun’s private interface to solve this problem. But then our code might not be portable on JVMs from other vendors.
Also, the private interfaces can be changed in future releases (general rules of backward compatibility do not apply to them), and therefore a possible change in our code.
What are the other ways of shutting down the JVM when the RMI Reaper thread (or any non-daemon thread) is still running?
- User interrupt such as ^C
- System-wide event, such as user logoff or system shutdown
While working with RMI, Java developers should ensure to clean-up the exported RMI remote objects, as otherwise, the JVM may not shutdown gracefully.