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

2023-05-16

写在前面

最近看众多框架源码的时候都看到使用到了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机制并不难,使用起来也是非常的简单,所以没啥好总结的~!

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM Shutdown Hook 机制原理以及源码分析 的相关文章

  • 内存中的方法表示是什么?

    在思考一下 Java C 编程时 我想知道属于对象的方法如何在内存中表示 以及这一事实如何涉及多线程 是为内存中的每个对象单独实例化一个方法还是执行 同一类型的所有对象共享该方法的一个实例 如果是后者 执行线程如何知道哪个对象是 要使用的属
  • Cassandra DB 中的日期插入:重要的 1 小时轮班问题(后续)

    这是这个的后续其他原帖 https stackoverflow com questions 23080188 date insertion in cassandra db non trivial 1h shift issue 2308355
  • 显示JVM中当前运行的所有线程组和线程

    所以我的任务是显示所有线程组以及当前在 JVM 中运行的属于这些组的所有线程 输出时应首先显示线程组 然后在下面显示该组中的所有线程 这是针对所有线程组完成的 目前 我的代码将仅显示每个线程组 然后显示每个线程 但我不确定如何达到我所描述的
  • Scala 泛型 - 为什么我无法在泛型类中创建参数化对象?

    我目前正在学习scala 为什么此代码不起作用 class GenClass T var d T var elems List T Nil def dosom x T var y new T y 我得到 错误 需要类类型 但找到了 T 代替
  • 估计 64 位 Java 中最大安全 JVM 堆大小

    在分析存在一些问题的 64 位 Java 应用程序的过程中 我注意到分析器本身 YourKit 正在使用真正大量的内存 我在 YourKit 启动脚本中得到的是 JAVA HEAP LIMIT Xmx3072m XX PermSize 25
  • LD_PRELOAD 和 strace 有什么区别?

    这两种方法都用于收集系统调用及其参数和返回值 当我们更愿意LD PRELOAD为什么 也许我们可以说我们只能通过以下方式收集系统调用strace但我们可以收集图书馆的电话LD PRELOAD诡计 然而 还有另一个库的跟踪器 其名称是ltra
  • git pre-status 或 post-status hook

    我想运行 lintergit status 不过似乎没有pre status nor post status hook 如何给 git 添加一个 hook The 精美文档 https git scm com book en v2 Cust
  • 当 Java 中的集合超出容量时会发生什么?

    我有一个服务 它将所有对其进行的调用暂存在内存中 因为我们不想丢失数据 同时我们需要该服务因任何外部依赖项 例如数据库 而失败 然后 这些分阶段的调用会在后台例行接收和处理 如果出于任何原因 如果调用太多并且内存不足 我们就需要警惕 所以
  • 如何在 WordPress 中创建“路线”?

    为了我自己的理智 我正在尝试为 ajax api 创建一条路由 如下所示 api
  • 使用主题函数在 body 标记后插入代码

    我试图在开头添加一段代码everyDrupal 站点中的页面 因为我有不止一个page模板 我想以编程方式执行此操作 但没有成功 我还是个新手 虽然我了解了钩子 主题函数等的要点 但我只是想不出实现这一目标的正确方法 到目前为止我已经覆盖了
  • 为什么不在下一个 JVM 中删除类型擦除呢?

    Java 在 Java 5 中引入了泛型类型擦除 因此它们可以在旧版本的 Java 上运行 这是兼容性的权衡 我们已经失去了这种兼容性 1 https stackoverflow com questions 22610400 a progr
  • 如何判断我是在 64 位 JVM 还是 32 位 JVM 中运行(在程序内)?

    如何判断应用程序运行的 JVM 是 32 位还是 64 位 具体来说 我可以使用哪些函数或属性来在程序中检测到这一点 对于某些版本的 Java 您可以使用标志从命令行检查 JVM 的位数 d32 and d64 java help d32
  • C# - 挂钩现有 COM 对象

    假设我们有一个现有进程 或应用程序 它从 ocx 文件 例如 MyCOMLibrary ocx 调用 COM 对象 有没有办法编写一个 C 库来精确复制 ocx 文件 这样原始应用程序就可以调用您的 C 代码而不是原始 COM 对象 当然
  • 低级键盘钩子不在 UI 线程上

    我想为键盘挂钩创建一个好的库 我使用 SetWindowsHookEx 方法 我注意到如果我的应用程序的主线程繁忙 则应在任何系统 KeyDown 事件中调用的方法 hookProc 不会执行 我认为钩子应该这样制作 以便另一个线程负责它
  • 如何在Java中计算对象的数字年龄[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想知道Java中对象的年龄 当我们使用new关键字时 Java中用户定义的对象被创建 但是什么时候它会被销毁 是跨越JVM的perm
  • 使用 javac 和 javax.tools.JavaCompiler 有什么区别?

    Maven 编译器插件文档states http maven apache org plugins maven compiler plugin 编译器插件用于编译项目的源代码 从 3 0 开始 默认编译器是 javax tools Java
  • 如何使用 Java 引用释放 Java Unsafe 内存?

    Java Unsafe 类允许您按如下方式为对象分配内存 但是使用此方法在完成后如何释放分配的内存 因为它不提供内存地址 Field f Unsafe class getDeclaredField theUnsafe Internal re
  • Java 语言中不可用的字节码功能

    当前 Java 6 是否有一些事情可以在 Java 字节码中完成而在 Java 语言中无法完成 我知道两者都是图灵完备的 所以将 可以做 理解为 可以做得更快 更好 或者只是以不同的方式 我正在考虑额外的字节码 例如invokedynami
  • 通过SOCKS代理连接Kafka

    我有一个在 AWS 上运行的 Kafka 集群 我想用标准连接到集群卡夫卡控制台消费者从我的应用程序服务器 应用程序服务器可以通过 SOCKS 代理访问互联网 无需身份验证 如何告诉 Kafka 客户端通过代理进行连接 我尝试了很多事情 包
  • Kotlin 未解决的参考:CLI 上 gradle 的 println

    放一个printlnkotlin 函数返回之前的语句会崩溃 堆栈跟踪 thufir dur NetBeansProjects kotlin thufir dur NetBeansProjects kotlin gradle clean bu

随机推荐