一、先提出一个问题
我们如果在JVM退出的时候做一些事情,比如关闭远程链接,怎么实现呢?
二、ShutdownHook简介
java里有个方法Runtime.getRuntime#addShutdownHook,是否了解呢?
ShutdownHook是什么意思呢,看单词解释“关闭钩子”,addShutdownHook就是添加一个关闭钩子,这个钩子是做什么的呢?能否解决上面的问题?
1、RunTime类
先看一下看源码RunTime#addShutdownHook方法与解释。
1.1 方法解释
核心意思,在Java虚拟机在关闭时会触发一些自己添加的事件。
Registers a new virtual-machine shutdown hook.
The Java virtual machine shuts down in response to two kinds of events:
The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or
The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.
A shutdown hook 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 exit method.
1.2 方法源码
public void addShutdownHook(Thread hook) {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
方法内部调用了ApplicationShutdownHooks#add,我们继续往下看。
2、ApplicationShutdownHooks类
2.1 添加钩子
private static IdentityHashMap<Thread, Thread> 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);
}
我们添加了一个钩子,这个钩子是个线程,这个线程怎么执行的呢? 继续看一下此类中的runHooks。
2.2 执行钩子
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
执行runHooks的时候,会启动所有的hook线程,什么时候调用runHooks方法的呢?
2.3 执行时机
为什么在系统退出的时候会执行添加的hook呢?我们看一下正常的退出操作System#exit方法。
1) 类调用层级
System->Runtime->Shutdown->ApplicationShutdownHooks
2) 方法调用
系统退出入口:System#exit
步骤 1-->System#exit
步骤 2-->Runtime#exit;
步骤 3--> Shutdown#exit
步骤 4--> Shutdown#runHooks
步骤 5--> ApplicationShutdownHooks#runHooks
步骤 6-->启动添加的hook线程
3) 补充一下
为什么步骤4会调用到步骤5呢?
可以看一下ApplicationShutdownHooks的构造函数,在创建的时候,封装了runHooks方法,放到了Shutdown的钩子集合里。
如此形成闭环,在系统正常退出的时候,最终执行我们添加的hook。
三、举个例子
了解了基本原理,我们看一下怎么使用的
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("等等我");
}
};
Runtime.getRuntime().addShutdownHook(thread);
System.out.println("程序关闭");
}
输出:
程序关闭
等等我
可以看到,在JVM退出的时候调用,执行了此线程,我们开发中,哪些场景可以使用呢?
四、应用场景
关闭链接、线程、资源释放、记录执行状态等。
五、风险点
1、长时间等待
如果添加的hook线程长时间执行,我们的退出命令会一直等待,为什么呢?
举个例子,我们在执行的时候sleep一下
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000*300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date()+" 等我5分钟");
}
};
Runtime.getRuntime().addShutdownHook(thread);
System.out.println(new Date()+" 程序关闭");
}
输出:
Tue Nov 12 17:37:38 CST 2024 程序关闭
Tue Nov 12 17:42:38 CST 2024 等我5分钟
2、原因
JVM在退出的时候会调用runHooks方法,看一下上面的方法java.lang.ApplicationShutdownHooks#runHooks方法。
关键字 hook.join(); 主线程会等待子线程执行完成。
如果程序一直执行,不能退出怎么办?
3、解决方案
1 ) 写代码时候控制执行逻辑、时长
2) kill -9 命令 强制退出
六、扩展
1、Runtime.getRuntime#addShutdownHook是面向开发者的
ApplicationShutdownHook#add、Shutdown#add我们都不能直接使用。
2、许多中间件框架也利用addShutdownHook来实现资源回收、清理等操作
比如Spring框架中,使用了ShutdownHook注册,我们常用的@PreDestroy在Bean销毁前执行一些操作,也是借助其回调的。
七、总结
1、本文简单介绍了一下ShutdownHook使用、原理、风险点。
2、我们工作中可以自己注册ShutdownHook,主动释放一些资源,降低风险。
3、小知识分享,不足之处欢迎大家指正,关于java里的知识点也欢迎大家讨论分享。