关于如何优雅的关闭JVM
使用的类及其方法:Runtime.addShutdownloadHook(Thread hook)
虚拟机关闭以及响应两种事件:
程序正常退出:最后一个守护线程退出或者调用exit(相当于System,exit)方法时
虚拟机响应用户中断:(键入^C)或者系统范围内的事件(用户注销或者系统关闭)而终止
/**
* Registers a new virtual-machine shutdown hook.
*
* <p> The Java virtual machine <i>shuts down</i> in response to two kinds
* of events:
*
* <ul>
*
* <li> The program <i>exits</i> normally, when the last non-daemon
* thread exits or when the {@link #exit exit} (equivalently,
* {@link System#exit(int) System.exit}) method is invoked, or
*
* <li> The virtual machine is <i>terminated</i> in response to a
* user interrupt, such as typing {@code ^C}, or a system-wide event,
* such as user logoff or system shutdown.
*
* </ul>
*
* <p> A <i>shutdown hook</i> is simply an initialized but unstarted
* thread. When the virtual machine begins its shutdown sequence it will
* start all registered shutdown hooks in some unspecified order and let
* them run concurrently. When all the hooks have finished it will then
* halt. Note that daemon threads will continue to run during the shutdown
* sequence, as will non-daemon threads if shutdown was initiated by
* invoking the {@link #exit exit} method.
*
* <p> Once the shutdown sequence has begun it can be stopped only by
* invoking the {@link #halt halt} method, which forcibly
* terminates the virtual machine.
*
* <p> Once the shutdown sequence has begun it is impossible to register a
* new shutdown hook or de-register a previously-registered hook.
* Attempting either of these operations will cause an
* {@link IllegalStateException} to be thrown.
*
* <p> Shutdown hooks run at a delicate time in the life cycle of a virtual
* machine and should therefore be coded defensively. They should, in
* particular, be written to be thread-safe and to avoid deadlocks insofar
* as possible. They should also not rely blindly upon services that may
* have registered their own shutdown hooks and therefore may themselves in
* the process of shutting down. Attempts to use other thread-based
* services such as the AWT event-dispatch thread, for example, may lead to
* deadlocks.
*
* <p> Shutdown hooks should also finish their work quickly. When a
* program invokes {@link #exit exit} the expectation is
* that the virtual machine will promptly shut down and exit. When the
* virtual machine is terminated due to user logoff or system shutdown the
* underlying operating system may only allow a fixed amount of time in
* which to shut down and exit. It is therefore inadvisable to attempt any
* user interaction or to perform a long-running computation in a shutdown
* hook.
*
* <p> Uncaught exceptions are handled in shutdown hooks just as in any
* other thread, by invoking the
* {@link ThreadGroup#uncaughtException uncaughtException} method of the
* thread's {@link ThreadGroup} object. The default implementation of this
* method prints the exception's stack trace to {@link System#err} and
* terminates the thread; it does not cause the virtual machine to exit or
* halt.
*
* <p> In rare circumstances the virtual machine may <i>abort</i>, that is,
* stop running without shutting down cleanly. This occurs when the
* virtual machine is terminated externally, for example with the
* {@code SIGKILL} signal on Unix or the {@code TerminateProcess} call on
* Microsoft Windows. The virtual machine may also abort if a native
* method goes awry by, for example, corrupting internal data structures or
* attempting to access nonexistent memory. If the virtual machine aborts
* then no guarantee can be made about whether or not any shutdown hooks
* will be run.
*
* @param hook
* An initialized but unstarted {@link Thread} object
*
* @throws IllegalArgumentException
* If the specified hook has already been registered,
* or if it can be determined that the hook is already running or
* has already been run
*
* @throws IllegalStateException
* If the virtual machine is already in the process
* of shutting down
*
* @throws SecurityException
* If a security manager is present and it denies
* {@link RuntimePermission}("shutdownHooks")
*
* @see #removeShutdownHook
* @see #halt(int)
* @see #exit(int)
* @since 1.3
*/
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
//获取系统安全接口。
//返回:如果已经为当前应用程序建立了安全管理器,则返回该安全管理器; 否则,返回null 。
public static SecurityManager getSecurityManager() {
return security;
}
/**
* Throws a <code>SecurityException</code> if the requested
* access, specified by the given permission, is not permitted based
* on the security policy currently in effect.
* <p>
* This method calls <code>AccessController.checkPermission</code>
* with the given permission.
*
* @param perm the requested permission.
* @exception SecurityException if access is not permitted based on
* the current security policy.
* @exception NullPointerException if the permission argument is
* <code>null</code>.
* @since 1.2
*/
/**
* (来自百度翻译)
* 如果基于当前有效的安全策略不允许给定权限指定的请求访问,则抛出SecurityException 。
* 此方法使用给定的权限调用AccessController.checkPermission 。
* 参数:perm - 请求的权限。
*/
public void checkPermission(Permission perm) {
java.security.AccessController.checkPermission(perm);
}
//此处的hook,即为先前在调用addShutdownHook时传入的线程
//hooks = new IdentityHashMap<>();
private ApplicationShutdownHooks() {}
/* Add a new shutdown hook. Checks the shutdown state and the hook itself,
* but does not do any security checks.
*/
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
以下来自百度翻译的关于addShutdownHook()方法的描述
关闭钩子在虚拟机生命周期的一个微妙时刻运行,因此应该进行防御性编码。 特别是,它们应该被编写为线程安全的,并尽可能避免死锁。 他们也不应该盲目依赖可能已经注册了自己的关闭钩子的服务,因此他们自己可能会在关闭过程中。 例如,尝试使用其他基于线程的服务(例如 AWT 事件分派线程)可能会导致死锁
关闭钩子也应该快速完成它们的工作。 当程序调用exit ,期望虚拟机将立即关闭并退出。 当虚拟机由于用户注销或系统关闭而终止时,底层操作系统可能只允许关闭和退出的固定时间量。 因此,不建议尝试任何用户交互或在关闭挂钩中执行长时间运行的计算
通过调用线程的ThreadGroup对象的uncaughtException方法,在关闭钩子中处理未捕获的异常就像在任何其他线程中一样。 此方法的默认实现将异常的堆栈跟踪打印到System.err并终止线程; 它不会导致虚拟机退出或停止。
在极少数情况下,虚拟机可能会中止,即在没有彻底关闭的情况下停止运行。 当虚拟机从外部终止时会发生这种情况,例如在 Unix 上使用SIGKILL信号或在 Microsoft Windows 上使用TerminateProcess调用。 如果本地方法出错,例如破坏内部数据结构或尝试访问不存在的内存,虚拟机也可能中止。 如果虚拟机中止,则无法保证是否会运行任何关闭挂钩
案例:
/**
* 优雅的关闭JVM
*/
public class ShutDownByElegance {
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(0);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
System.out.println(Thread.currentThread().getName() + "==>" + i);
integer.addAndGet(1);
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread_01");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
System.out.println(Thread.currentThread().getName() + "==>" + i);
integer.addAndGet(1);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread_02");
Thread thread3 = new Thread(() -> {
System.out.println("哇卡卡卡卡~,所有事情整完了,终于可以结束了,让我们瞅瞅共循环了几次吧!。");
System.out.println(integer);
}, "Thread_03");
thread2.setDaemon(true);
thread1.start();
thread2.start();
/**
* 此种方式只能保证在正常结束时,在最后执行thread3
*/
Runtime.getRuntime().addShutdownHook(thread3);
}
}