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

news2024/9/23 11:23:37

写在前面

最近看众多框架源码的时候都看到使用到了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.coloradmin.cn/o/505838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PS如何安装ZXP扩展插件?

Photoshop如何安装ZXP扩展插件&#xff1f;有些小伙伴不会安装&#xff0c;本文介绍两种安装ZXP扩展的方法&#xff0c;希望对您有帮助。 方法一&#xff1a;手动安装方式 1、把下载好的.zxp扩展名改为.zip&#xff0c;然后解压。 Windows系统&#xff1a;C:\Users[ USER ]\A…

CSS--定位

01-定位 作用&#xff1a;灵活的改变盒子在网页中的位置 实现&#xff1a; 1.定位模式&#xff1a;position 2.边偏移&#xff1a;设置盒子的位置 leftrighttopbottom 相对定位 position: relative 特点&#xff1a; 不脱标&#xff0c;占用自己原来位置显示模式特点保…

系统安全分析与设计

目录 第五章、系统安全分析与设计1、信息系统安全属性2、对称加密技术与非对称加密技术3、信息摘要4、数字签名5、数字信封与PGP6、网络安全6.1、各个网络层次的安全保障6.2、网络威胁与攻击6.3、防火墙 第五章、系统安全分析与设计 1、信息系统安全属性 安全属性 保密性&…

最近想学PMP,有什么要注意和推荐的吗?

我觉得参加PMP的学习和考试有两点需要把握住&#xff0c;一是心态&#xff0c;二是学习方法&#xff1b; 谈心态的话虽然比较虚&#xff0c;因为这个还是要看个人在生活中对事物发展的应对能力与应对突发情况的处理能力&#xff0c;但是简单的谈谈在备考过程中心态的处理还是很…

Docker私有仓库

Docker私有仓库 私有仓库搭建 首先启动docker 拉取私有仓库镜像 [rootserver-a ~]# docker pull registry Using default tag: latest latest: Pulling from library/registry 79e9f2f55bf5: Pull complete 0d96da54f60b: Pull complete 5b27040df4a2: Pull complete e2ead82…

「PDF转长图」无压力:两种简单易学的转换方法!

在加班赶DDL的晚上&#xff0c;你突然接到老板的信息&#xff1a;立刻将这份PDF文件转换成长图并发给我&#xff01;于是你开始了疯狂截图的模式。你是否曾经遇到过这个问题&#xff0c;不知道是否有方便快捷的PDF转长图的解决方法呢&#xff1f; 作为一名资深的PDF专家&#x…

【26】核心易中期刊推荐——机器智能人工神经网络

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

诗圣杜甫不同时期的代表作

杜甫一生大致分为四个时期。 中青年时期 青年时&#xff0c;作为官三代的杜甫并不缺钱&#xff0c;四处游历&#xff0c;与李白、高适唱和、仙游&#xff0c;成为佳话。这个时期杜甫的作品热血豪迈&#xff0c;气势蓬勃。代表作首推《望岳》&#xff1a; 岱宗夫如何&#xf…

TCP三次握手,四次挥手

为什么是三次握手&#xff0c;两次or四次为什么不行&#xff1f; 两次握手&#xff1a;客户端发&#xff0c;服务收到后发&#xff0c;两方即可建立连接 存在的问题&#xff1a;这个过程确认了客户端发送能力正常&#xff0c;服务端发送&#xff0c;接受能力正常&#xff0c;…

商户查询的缓存——添加redis缓存

1.什么是缓存 缓存就是数据交换的缓冲区&#xff08;Cache&#xff09;,是存储数据的临时地方&#xff0c;一般读写性能较高 2.添加redis缓存 Autowired private StringRedisTemplate stringRedisTemplate; /*** 通过id查询商户信息* param id* return*/ Override public Resu…

【Python】五子棋 —— 摸鱼必备的小项目~

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,YOLO领域博主爱笑的男孩。擅长深度学习,活动,YOLO,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个人简…

Kali-linux设置ProxyChains

ProxyChains是Linux和其他Unices下的代理工具。它可以使任何程序通过代理上网&#xff0c;允许TCP和DNS通过代理隧道&#xff0c;支持HTTP、SOCKS4和SOCKS5类型的代理服务器&#xff0c;并且可配置多个代理。ProxyChains通过一个用户定义的代理列表强制连接指定的应用程序&…

【Redis扩展】Redis两种高级数据结构-HyperLogLog、BitMap

一、HyperLoglog-基数统计 1. HyperlogLog数据类型特点 Redis HyperLogLog 是用来做基数统计的算法&#xff0c;用以完成独立总数的统计HyperLogLog 的优点是&#xff0c;在输入元素的数量或者体积非常非常大时&#xff0c;计算基数所需的空间总是固定的、并且是很小的。花费…

springboot整合邮箱功能二(实战)

【SpringBoot整合Email发送邮件】_ζั͡ ั͡空 ั͡ ั͡白&#xfffd;的博客-CSDN博客 https://www.cnblogs.com/erlou96/p/16878192.html#_label1_5 1. 准备工作 1.1 qq邮箱设置 本文默认使用qq邮箱来发送邮件,然后使用一个在线临时邮箱来接收邮件。为了让程序能够通过…

Semantic Segmentation using Adversarial Networks代码

代码来源 首先看一下模型架构&#xff1a; 损失计算&#xff1a; class GANUpdater(chainer.training.StandardUpdater, UpdaterMixin):def __init__(self, *args, **kwargs):self.model kwargs.pop(model) # set for exeptions.Evaluatorself.gen, self.dis self.model[g…

O2OA中如何使用PostgreSQL + Citus 实现分布式数据库实现方案?

虽然 O2OA 数据表高效的表结构以及索引的设计已经极大程度地保障了数据存取操作的性能&#xff0c;但是随着使用时间从增长&#xff0c;数据表存放的数据量也会急剧增长。此时&#xff0c;仍然需要有合适的方案来解决数据量产生的系统性能瓶颈。本文介绍通过 PostgreSQL Citus…

2023年5月DAMA-CDGA/CDGP数据治理认证开班啦,我要报名学习

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中&#xff01; 考试开放地区&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证班进行中&#xff0c;报名从速&#xff01; DAMA认证为数据…

【刷题之路】LeetCode 234. 回文链表

【刷题之路】LeetCode 234. 回文链表 一、题目描述二、解题1、方法1——复制值到数组后用双指针1.1、思路分析1.2、代码实现 2、方法2——反转另一半链表2.1、思路分析2.2、代码实现2.3、补充 3、方法3——递归3.1、思路分析3.2、代码实现 一、题目描述 原题连接&#xff1a; …

计算机图形学 | 裁剪与屏幕映射

计算机图形学 | 裁剪与屏幕映射 计算机图形学 | 裁剪与屏幕映射8.1 裁剪思想裁剪的概念编码裁剪法中点裁剪法Liang-Barsky算法 8.2 真正的裁剪——在三维空间遇见多边形真正的裁剪多边形的裁剪Weiler-Atherton算法三维空间中的裁剪 8.3 几何阶段的完结&#xff1a;屏幕映射屏幕…

API 接口的使用和功能

随着互联网的快速发展&#xff0c;API接口已经成为了现代开发中不可或缺的一部分。API接口可以让你的应用程序与其他应用程序、系统或服务进行数据交流和集成。如果你正在开发应用程序&#xff0c;那么最好的方法就是使用API接口来增强功能和性能。 我们的API接口是为您的应用…