Hook(钩子)线程

一、关闭钩子介绍

处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。

Java shutdown hook 很方便在程序退出时运行一些代码。我们可以使用 java.lang.addshutdownhook (Thread hook) 方法在 JVM 中添加 shutdown hook。

在 JVM 程序即将退出的时候,Hook 线程就会被启动执行。

1、java.lang.Runtime.addShutdownHook(Thread hook) 方法

1
public void addShutdownHook(Thread hook){//省略...}

hook: 初始化但未启动的 Thread 对象。

可以通过调用静态工厂方法 getRuntime() 来获取 Runtime 类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
//...
}
});
或者 Runtime.getRuntime().addShutdownHook(new Thread(() -> {/*...*/});
或者传入Runnable实例

//通过调用静态工厂方法 getRuntime() 来获取 Runtime 类的对象
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() { return currentRuntime; }
}

2、shutdown hook 什么情况下执行?

Java shutdown hook(关闭钩子)在这两种情况下运行(JVM正常关闭):

​ (1)最后一个非守护线程被退出 或者 调用exit退出方法时(如System.exit(0)),程序正常退出。

​ (2)响应于用户中断(例如,键入 ^ C)或系统范围的事件(例如用户注销或系统关闭),终止虚拟机。如果通过 kill -9 强制杀死 java 进程,不会执行 Hook 线程。

仅在很少的情况下,虚拟机可能会中止,也就是没有完全关闭就停止运行。这种情况会发生在虚拟机被从外部终止时,比如在 Unix 上使用 SIGKILL 信号或在 Microsoft Windows 上调用 TerminateProcess 。如果由于例如内部数据结构损坏或试图访问不存在的内存而导致本地方法执行错误,虚拟机也可能会中止。如果虚拟机中止,则不能保证所有的关闭钩子都被运行。

3、Java Shutdown Hook 的要点

虚拟机开启关闭序列时,它会以不确定的顺序启动全部注册的关闭钩子,并允许它们并发运行。

运行完所有的钩子后,如果已启用 finalization-on-exit,那么虚拟机接着会运行所有未调用的 finalizers。最后,虚拟机会暂停。

注意,如果关闭是通过调用exit方法启动的,则守护程序线程将在关闭序列中继续运行,非守护程序线程也将继续运行。

一旦关闭序列开始执行,只能通过调用 halt 方法停止它,该方法可强制终止虚拟机:Runtime.getRuntime().halt(status)

一旦关闭序列开始执行就不能注册一个新的关闭钩子或者取消注册已经注册的钩子。尝试这些操作会导致抛出 IllegalStateException 。

关闭钩子应该编写为线程安全的,并且尽可能避免死锁。关闭钩子还应该不盲目地依靠某些服务。钩子线程不应该执行耗时操作,需尽快执行结束。

3、Hook 线程的应用场景

​ 1)防止程序重复执行。具体实现可以在程序启动时,校验是否已经生成 lock 文件,如果已经生成,则退出程序,如果未生成,则生成 lock 文件,程序正常执行,最后再注入 Hook 线程(线程中执行删除 lock 操作),这样在 JVM 退出的时候,线程中再将 lock 文件删除掉;

这种防止程序重复执行的策略,也被应用于 Mysql 服务器,zookeeper, kafka 等系统中。

​ 2)Hook 线程中也可以执行一些资源释放的操作,比如关闭数据库连接,Socket 连接,关闭线程池等。

二、如何屏蔽第三方组件的ShutdownHook

有一些第三方组件在代码中注册了关闭自身资源的 ShutdownHook,比如 dubbo,在 static 方法块里面注册了自己的关闭钩子,完全不可控。

从 Runtime.java 和 ApplicationShutdownHooks.java 的源码中,我们看到并没有一个可以遍历操作 shutdownHook 的方法。

Runtime.java 仅有的一个 removeShutdownHook 的方法,对于未写线程名的匿名类来说,无法获取对象的引用,也无法分辨出彼此。

ApplicationShutdownHooks.java 不是 public 的,类中的 hooks 也是 private 的。

只有通过反射的方式才能获取并控制它们

三、System.exit(int)介绍

Java.lang.System 包下:

1
2
3
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}

System.exit()调用的是Runtime对象的exit()方法,二者等价,该方法不会返回任何值。

  • 这个方法是用来结束当前正在运行中的 java 虚拟机。

  • status为0:System.exit(0)表示正常退出 ,将整个虚拟机里的内容都关掉,内存都释放掉。

    status非 0 :如System.exit(1),表示非正常退出程序。一般用在程序未按预期执行需要停止程序时。

  • 在try/catch块中有System.exit()来退出JVM,这种情况下finally块不会执行。

  • 通过System.exit()关闭程序,在 JVM 退出之前,系统注册的关闭钩子将会被调用。