JVM Shutdown Hook 机制原理以及源码分析

news/2025/1/16 0:08:46/

写在前面

最近看众多框架源码的时候都看到使用到了Shutdown Hook机制。比如下图:SkyWalking、Spring、Tomcat等等框架,几乎只要是Java层面的框架都会使用到此机制。所以,借用论坛给读者写一篇关于JVM Shutdown Hook 机制原理分析以及源码分析。

 

Shutdown Hook 机制原理:

这里就不提供代码案例展示了,因为上面几个框架源码已经展示的很明显了。

JVM提供的一个hook机制,在JVM关闭之前JVM自动触发开发者实现的hook。开发者可以在运行期间动态添加或者删除hook。此机制目的也很简单,让开发者可以在JVM关闭之前做收尾工作,合理的释放自己想释放的资源,而不是全部委托给JVM来释放。 大致流程图如下:

源码分析: 

从上述的大致流程图可以分析得出源码分为2个步骤:

  1. Java层面如何注册ShutdownHook任务
  2. JVM层面如何回掉所有的ShutdownHook任务

先从读者都能看懂的Java层面入手

Runtime.getRuntime().addShutdownHook(new Thread(()-> System.out.println("shutdownHook..")));public void addShutdownHook(Thread hook) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("shutdownHooks"));}ApplicationShutdownHooks.add(hook);
}

这里调用了ApplicationShutdownHooks.add方法,把传入的Thread线程添加进去。

class ApplicationShutdownHooks {private static IdentityHashMap<Thread, Thread> hooks;static {try {// 调用Shutdown类的静态方法add,注册一个runnable任务。Shutdown.add(1 ,false,new Runnable() {public void run() {// runnable回掉runHooks方法。runHooks();}});hooks = new IdentityHashMap<>();} }// 传入线程和任务体static synchronized void add(Thread hook) {…………// 添加到全局集合中,等待被回掉。hooks.put(hook, hook);}// 回掉方法。static void runHooks() {Collection<Thread> threads;synchronized(ApplicationShutdownHooks.class) {threads = hooks.keySet();hooks = null;}// 执行注册的所有的hook。for (Thread hook : threads) {hook.start();}// 等待所有的hook执行完毕。for (Thread hook : threads) {while (true) {try {hook.join();break;} catch (InterruptedException ignored) {}}}}
}

这里描述出来可能比较绕,因为存在多次回掉,笔者认为代码不难,应该读者都能够自己看明白。不过这里还是对以上代码做一个总结。

  1. Runtime.getRuntime().addShutdownHook方法调用了ApplicationShutdownHooks.add(hook); 把线程对象传入。
  2. add方法内部非常简单把线程对象添加到全局的hooks集合中(注意这里的一切都是static修饰的,所以是类共享的,也即是唯一的)
  3. ApplicationShutdownHooks类中static静态代码块中调用Shutdown.add方法,注册了一个Runnable任务。
  4. Runnable任务的run任务体执行的是runHooks方法,也即最终会回掉runHooks方法。
  5. runHooks方法就非常的明显了,启动全局hooks线程集合的线程对象(这也对应上第一点),并且此处阻塞直到所有的线程执行完毕。
  6. 所以接下来需要分析Shutdown类做了些什么。
class Shutdown {// 全局的Runnable数组private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {synchronized (lock) {…………// 添加到全局数组中hooks[slot] = hook;}}private static void runHooks() {for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {try {Runnable hook;synchronized (lock) {currentRunningHook = i;hook = hooks[i];}if (hook != null) // 回掉Runnable,也即执行ApplicationShutdownHooks类中runHooks方法hook.run();} }}private static void sequence() {synchronized (lock) {if (state != HOOKS) return;}// 执行runHooks方法runHooks();boolean rfoe;synchronized (lock) {state = FINALIZERS;rfoe = runFinalizersOnExit;}if (rfoe) runAllFinalizers();}static void shutdown() {synchronized (lock) {switch (state) {case RUNNING:       /* Initiate shutdown */state = HOOKS;break;case HOOKS:         /* Stall and then return */case FINALIZERS:break;}}synchronized (Shutdown.class) {// 执行sequence方法sequence();}}
}

Shutdown类中add方法接收ApplicationShutdownHooks类static静态代码块传入的Runnable任务,添加到全局的Runnable数组中。在Shutdown类中shutdown方法回掉sequence方法,而在sequence方法中回掉runHooks方法,而在Shutdown类中runHooks方法回掉Runnable任务。而回掉Runnable任务就是在执行执行ApplicationShutdownHooks类中runHooks方法。而执行ApplicationShutdownHooks类中runHooks方法就等于在启动开发者传入的Thread线程对象,最终执行开发者的自定义逻辑。

所以接下来只需要找到Shutdown类中shutdown方法调用处就全部闭环啦。到此,可能对于大部分的Java程序员找破头都找不出来哪里调用的。没错,这里是JVM调用的,所以接下来是JVM源码部分。

bool Threads::destroy_vm() {JavaThread* thread = JavaThread::current();{ MutexLocker nu(Threads_lock);// 这里对应上一句八股文,主线程执行完毕后,JVM关闭需要等待其他非守护线程执行完毕。while (Threads::number_of_non_daemon_threads() > 1 )// 阻塞等待。Threads_lock->wait(!Mutex::_no_safepoint_check_flag, 0,Mutex::_as_suspend_equivalent_flag);}…………// JDK12的特殊处理if (JDK_Version::is_jdk12x_version()) {HandleMark rm(thread);Universe::run_finalizers_on_exit();} else {// 执行Java程序注册的ShutdownHooks。thread->invoke_shutdown_hooks();}thread->exit(true);…………return true;
}

这里是JVM执行完main主线程后摧毁main主线程的逻辑代码。

也非常的简单,这里需要阻塞等待其他非守护线程执行完毕,然后执行invoke_shutdown_hooks方法去回掉Java程序注册的ShutdownHooks逻辑。

// 来自vmSymbols.hpp 映射表
template(shutdown_method_name,                      "shutdown")    
template(void_method_signature,                     "()V")  void JavaThread::invoke_shutdown_hooks() {…………// 拿到java_lang_Shutdown类对象。也即拿到Shutdown类对象。Klass* k =SystemDictionary::resolve_or_null(vmSymbols::java_lang_Shutdown(),THREAD);if (k != NULL) {instanceKlassHandle shutdown_klass (THREAD, k);JavaValue result(T_VOID);// 调用Shutdown类的shutdown方法。JavaCalls::call_static(&result,shutdown_klass,vmSymbols::shutdown_method_name(),vmSymbols::void_method_signature(),THREAD);}
}

到此,就全部闭环啦~!

总结:

大部分笔者在Java层面如鱼得水,但是C/C++层面无从下手。所以想扩宽道路还是得多往底层学习。

ShutdownHook机制并不难,使用起来也是非常的简单,所以没啥好总结的~!

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!


http://www.ppmy.cn/news/65628.html

相关文章

【分布式事务】Seata 之 @GlobalTransactional 在TM侧的核心逻辑

文章目录 一、概述二、GlobalTransactional核心逻辑三、GlobalTransactional核心源码解读四、事务能力的启停运行期的开关变更 一、概述 Seata 依赖 Spring 的注解机制&#xff0c;实现声明式事务&#xff0c;即开发者给 Bean 使用GlobalTransactional注解 &#xff0c;Seata …

Python基础入门编程代码练习(六)

一、模拟房产经纪人来管理房屋信息 编写业务实现 家具类&#xff1a;HouseItem 属性&#xff1a;名字 name&#xff0c;占地面积 area 方法&#xff1a;__init__ , __str__ 类名&#xff1a;房子类 House 属性&#xff1a;户型 name&#xff0c;总面积&#xff1a;total_are…

Restormer Efficient Transformer for High-Resolution Image Restoration论文代码运行记录

文章目录 Restormer代码训练和测试运行记录文章及代码地址1. 所需环境2. 配置环境3. 安装gdrive以便下载数据集4. 放置权重文件5. 运行Demo运行单图像散焦去模糊训练、测试 Restormer代码训练和测试运行记录 文章及代码地址 文章名称&#xff1a;Restormer: Efficient Transf…

分布式系统之CAP定理介绍

前言 在分布式系统的设计和实现中&#xff0c;CAP定理是一个非常重要的概念。本文将介绍CAP定理的概念、含义和应用。 什么是 CAP 定理&#xff1f; CAP定理是分布式系统设计中的一个基本原则&#xff0c;它指出&#xff0c;在分布式系统中&#xff0c;一致性&#xff08;Consi…

算法修炼之练气篇——练气十八层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

【C++】| 03——STL | 迭代器

系列文章目录 【C】| 01——泛型编程 | 模板 【C】| 02——STL | 初识 【C】| 03——STL | 迭代器 【C】| 04——STL | 容器_vector 文章目录 1. 什么是迭代器2. 迭代器的分类3. 不同容器对应的迭代器4. 迭代器的好处5. 迭代器的操作 1. 什么是迭代器 迭代器就是指向容器内元素…

USBCAN卡和台达AS228T通信

AS228R 如图 A.把CAN卡固定上 B.接一个120欧的终端电阻 C.把来可的USBCAN卡的CANH和CANL分别接入AS228R的CANH和CANL. CAN 接口及网络拓扑 10.2.4.1 CAN 网络实体信号的定义及数据格式 CAN 信号为差分信号&#xff0c;信号电压为 CAN和 CAN-之间的电压差&#xff0c;CAN 和…

C++之多态与虚函数

文章目录 初识多态运行时多态的原理静态联编和动态联编 初识多态 多态性是面向对象程序设计的关键技术之一。若程序不支持多态&#xff0c;不能称为面向对象的语言编译时多态&#xff1a;通过函数重载实现&#xff0c;早期绑定运行时多态&#xff1a;在程序执行过成中&#xf…