JVM

JVM Shutdown Hook

一个程序退出前执行指定任务的方法

Posted by Jeremy Song on 2023-03-21
Estimated Reading Time 5 Minutes
Words 1.2k In Total
Viewed Times

今天偶然看到 java.lang.Runtime 类的一个方法 public void addShutdownHook(Thread hook)

它的 javadoc 是这么写的:

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.

意思就是说可以给 JVM 注册一个钩子,这个钩子将在虚拟机关闭的执行。当然这个 关闭 是有条件的。

写个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ShutdownHookTest {

public static void main(String[] args) {
System.out.println("Main thread start");
Thread hook = new Thread(new MyShutdownHook());
Runtime.getRuntime().addShutdownHook(hook);
System.out.println("Main thread end");
}

static class MyShutdownHook implements Runnable {
@Override
public void run() {
System.out.println("-- my shutdown hook start --");
try {
System.out.println("-- do hook task --");
Thread.sleep(5 * 1000L);
System.out.println("-- hook task finished --");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-- my shutdown hook end --");
}
}

}

使用 javac 编译后运行,得到的输出结果是:

1
2
3
4
5
6
Main thread start
Main thread end
-- my shutdown hook start --
-- do hook task --
-- hook task finished --
-- my shutdown hook end --

结果符合文章刚开始引文的第一个情况,当程序的正常退出时会执行注册的钩子。也就是说,在程序主线程(实际上是所有的 demon线程)结束后,会启动执行钩子线程。

程序非执行完成推出的例子

稍微改一下上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ShutdownHookTest {

public static void main(String[] args) throws Exception{
System.out.println("Main thread start");
Thread hook = new Thread(new MyShutdownHook());
Runtime.getRuntime().addShutdownHook(hook);
// --- 改了这里 ---
Thread.sleep(120 * 1000L);
// --------------
System.out.println("Main thread end");
}

static class MyShutdownHook implements Runnable {
@Override
public void run() {
System.out.println("-- my shutdown hook start --");
try {
System.out.println("-- do hook task --");
Thread.sleep(5 * 1000L);
System.out.println("-- hook task finished --");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-- my shutdown hook end --");
}
}

}

同样使用 javac 编译后运行。不同的是,在程序启动后按 ctrl + c 停止程序,将得到下面的输出:

1
2
3
4
5
Main thread start
-- my shutdown hook start --
-- do hook task --
-- hook task finished --
-- my shutdown hook end --

这个结果也符合文章开始引文的第二个情况。当虚拟机用为用户输入 ^C 时,虚拟机会调用已注册的钩子。

另外因为也提到了当用户注销系统关闭时也会调用已注册的钩子,这里就不做验证了。

钩子不能执行的情况

同样是上面的例子。程序在启动后,打开任务管理器(Windows),找到对应的进程并结束。这时控制台的输出为:

1
Main thread start

从输出可以看到,钩子并没有执行。

这就说明,在虚拟机中止 (注意:这里的中止不同于退出或停止,是指异常的break) 的情况下,钩子不会被调用执行。

javadoc 也给出了这种情况的说明:

在极少数虚拟机被外部中止的情况下,例如:

  • 在 Unix 上使用 SIGKILL 信号
  • 在 Windows 上使用 TerminateProcess 调用
  • 执行本地方法出错

也就是说,虚拟机在没有干净地关闭的情况下停止运行,虚拟机则不能保证是否正确的运行关机钩子。

移除钩子

移除钩子使用方法 public boolean removeShutdownHook(Thread hook) 即可。

使用场景

看到这个特性,第一个想到的场景就是。程序在关闭时可以记录一个日志或发送一个通知。

当然,这个基于这个特性,可以定制出来很多使用场景。

但是,鉴于这个钩子的执行时机,就有很多需要注意的地方:

  • 关机钩子(shutdown hook)必须是一个已初始化但未启动的线程
  • 如果注册了多个钩子,则不能保证这些钩子的执行顺序,他们是同时开始的
  • 当虚拟机关机序列开始,则无法在注册或取消钩子
  • 对于钩子的线程的编写,应该是线程安全的,并尽可能地避免出现死锁
  • 钩子的执行时间不应过长,也不应该添加任何用户交互功能
    • 这时因为当用户注销或者关机时,底层操作系统可能只允许有限的固定时间来关闭和退出虚拟机

最后

文章就写到这里。

这篇文章没啥深入的探究,只是突然发现了一个之前未曾注意到的功能,简单的做一下记录和测试。

大家如果要在生产环境中使用,要是场景复杂还是慎重些,上一章节的那些注意事项都需要考虑考虑。


欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !