shutdownHook
是一种特殊的结构,它允许开发人员插入JVM关闭时执行
的一段代码。这种情况在我们需要做特殊清理操作
的情况下很有用
用途
在Jboss
,Jetty
等容器中都可以看到shutdownHook
的身影,例如在服务优雅下线一文中的spring-boot-starter-actuator
就会触发shutdownHook
...
-
Application
正常退出,在退出时执行特定的业务逻辑,或者关闭资源等操作。 - 虚拟机非正常退出,比如用户按下ctrl+c、OOM宕机、操作系统关闭(kill pid)等。在退出时执行必要的挽救措施。
用法
正常退出
public class ShutdownHook {
public static void main(String[] args) throws InterruptedException {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try (FileWriter fw = new FileWriter("hook.log")) {
// 假设记录日志/或者发送消息
fw.write("完成销毁操作,回收内存! " + (new Date()).toString());
System.out.println("退出程序...");
} catch (IOException e) {
e.printStackTrace();
}
}));
IntStream.range(0, 10).forEach(i -> {
try {
System.out.println("正在工作...");
Thread.sleep(2_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
当我们运行上面的代码时,会看到在完成main方法的执行时JVM调用shutdownHook
。
正在工作...
...
正在工作...
退出程序...
kill pid方式
注意事项
虽然编写一个shutdownHook
很简单,但是需要了解shutdownHook
后面的内部部件才能正确使用。因此,在后续中,将探讨shutdownHook
设计背后的一些陷阱
。
应用程序无法保证shutdownHook
总是运行的
如JVM由于某些内部错误而崩溃,或(Unix / Linux中的kill -9)或TerminateProcess(Windows)),那么应用程序需要立即终止而不会甚至等待任何清理活动。除了上述之外,还可以通过调用Runime.halt()
方法来终止JVM,而阻止shutdownHook
运行。
shutdownHook
可以在完成前强行停止
虽然shutdownHook
开始执行,但是在操作系统关闭的情况下,任然可以在完成之前终止它。在这种情况下,一旦SIGTERM
被提供,O/S
等待一个进程终止指定的时间。如果进程在该时间限制内没有终止,则O/S
通过发出SIGTERM(或Windows中的对等方)强制终止进程。所以有可能这是在shutdownHook
中途执行时发生的。
因此,建议谨慎地编写shutdownHook
,确保它们快速完成,并且不会造成死锁等情况。另外特别注意的是,不应该执行长时间计算或等待用户I/O操作在钩子。
可以有多个shutdownHook
,但其执行顺序无法保证
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap hooks;
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);
}
}
通过源码发现,可以注册多个shutdownHook
。但是因为它是存储在IdentityHashMap
中的,JVM并不能保证其执行顺序。但是可以同时执行所有的shutdownHook
关闭顺序开始后,无法注册/取消注册shutdownHook
一旦关闭顺序是由JVM发起的,将不在允许添加或删除任何现有的shutdownHook
,否则抛出IllegalStateException
异常。
关闭顺序开始后,只能由Runtime.halt()
停止
关闭顺序开始后,只能通过Runtime.halt()(强制终止JVM)
,可以停止关闭顺序的执行(外部影响除外,如SIGKILL)。
使用shutdownHook
需要安全权限
如果我们使用Java Security Managers
,则执行添加/删除shutdownHook
的代码需要在运行时
具有shutdownHooks
权限。否则会导致SecurityException
。
参考:http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread) "官方文档")
- 说点什么
全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter10
- 个人QQ:1837307557
- battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)