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 机制原理以及源码分析 的相关文章

  • 忽略 Mercurial hook 中的某些 Mercurial 命令

    我有一个像这样的善变钩子 hooks pretxncommit myhook python path to file myhook 代码如下所示 def myhook ui repo kwargs do some stuff 但在我的例子中
  • 查找哪个程序运行另一个程序

    我有一个 NAS 运行在 Redhat Linux 的有限版本上 我按照指示破解了它 这样我就可以访问 shell 这很有帮助 我还做了一些修改 其他人也做过修改 除了一个问题之外 它们似乎都工作得很好 不知何故 每隔 22 天 系统就会关
  • 线程上下文类加载器和普通类加载器的区别

    线程的上下文类加载器和普通类加载器有什么区别 也就是说 如果Thread currentThread getContextClassLoader and getClass getClassLoader 返回不同的类加载器对象 将使用哪一个
  • 测量 tomcat 的排队请求数

    因此 使用tomcat 您可以设置acceptCount值 默认为100 这意味着当所有工作线程都忙时 新连接被放置在队列中 直到队列满 之后它们被拒绝 我想要的是监视此队列中项目的大小 但无法确定是否有办法通过 JMX 获取此值 即不是队
  • Android java.exe 以非零退出值 1 结束 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我尝试过查看类似的解决方案 但没有解决方案有效 我以前运行应用程序没有问题 但我的新应用程序突然开始给我带来问题 当我尝试运行它时总是
  • 如何在没有 Node.JS 的情况下运行 UglifyJS2

    无论如何都要跑UglifyJS2 https github com mishoo UglifyJS2没有node js 假设我想使用 JavaScript 脚本引擎在 JVM 进程中运行它 怎么做 我看到米秀回答你了https github
  • JVM锯齿状空闲进程

    我目前正在进行一项涉及 JVM 及其内存使用工作原理的研究 我不明白的是 JVM在空闲时用什么填充它的内存 只是为了在堆几乎达到时释放它 为什么使用的内存不只有一条平线 顺便说一句 这个 java 应用程序托管在 glassfish 上 但
  • Dart/Flutter 如何编译到 Android?

    我找不到任何具体的资源 Dart 是否被编译到 JVM 或者 Google 的团队是否编译了 Dart VM 以在 JVM 上运行 然后在 JVM 内的 Dart VM 中运行 Dart 前者更有意义 并且符合 无桥 的口号 但后者似乎更符
  • 是否可以强制 JVM 在堆中而不是堆中创建对象?

    我读过一些文章 有时JVM会识别一些对象并尝试在堆栈中而不是堆中创建它 因为堆栈上的内存分配比堆中的内存分配便宜 堆栈上的释放是免费的 并且堆栈由以下方式有效管理 运行时 那么 堆栈中的对象分配是如何工作的 有什么方法可以强制 JVM 执行
  • 当前向包含多个自动分级节点时,PyTorch 关于使用非完整后向挂钩的警告

    最近升级后 当运行 PyTorch 循环时 我现在收到警告 当前向包含多个自动分级节点时使用非完整后向钩子 训练仍在运行并完成 但我不确定应该将其放置在哪里register full backward hook功能 我尝试将它添加到神经网络
  • 了解 Sun JVM [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • pthread_key_create 析构函数没有被调用

    As per pthread key create http www manpagez com man 3 pthread key create 手册页中 我们可以关联一个在线程关闭时调用的析构函数 我的问题是我注册的析构函数没有被调用 我
  • 从 Java 内部限制 CPU

    我在这个 和其他 论坛中看到了许多具有相同标题的问题 但似乎没有一个问题能完全解决我的问题 就是这个 我有一个 JVM 它占用了托管它的机器上的所有 CPU 我想限制它 但是我不能依赖任何限制工具 技术external到 Java 因为我无
  • JVM 最大堆大小可以是动态的吗?

    JVM Xmx 参数允许将 JVM 的最大堆大小设置为某个值 但是 有没有办法让这个价值动态化呢 换句话说 我想告诉 JVM 看 如果你需要它 就继续从系统中获取 RAM 直到系统退出 提问原因分为两部分 首先 所讨论的应用程序可以根据用户
  • 从不同 JVM 中的 Java 桌面应用程序中执行 Java main 方法

    我有一个桌面应用程序 当有人按下按钮时 我希望它启动另一个执行类的 main 方法的 JVM 我的桌面应用程序已经依赖于包含具有我想要执行的 main 方法的类的 jar 目前我有以下代码 但是 我希望它们是一种更优雅的方法 Runtime
  • 限制 Java 进程的总内存消耗(在 Cloud Foundry 中)

    与这两个问题相关 如何设置JVM的最大内存使用量 https stackoverflow com questions 1493913 how to set the maximum memory usage for jvm 什么会导致 jav
  • 运行具有外部依赖项的 Scala 脚本

    我在 Users joe scala lib 下有以下 jar commons codec 1 4 jar httpclient 4 1 1 jar httpcore 4 1 jar commons logging 1 1 1 jar ht
  • 如何使用 VBA 添加 MS Outlook 提醒事件处理程序

    我想扩展 MS Outlook 以便当弹出日历提醒时 我可以运行一个可以运行外部程序 如批处理脚本 的 VBA 挂钩 就我而言 我想将提醒 转发 到 Linux 桌面 因为我在这两种环境中工作 并且 Windows 桌面并不总是可见 我看到
  • 附加到已经运行的 JVM

    有没有办法附加到已经运行的 JVM 例如 在 JNI 中您可以使用JNI CreateJavaVM创建一个虚拟机并运行一个 jar 并检查它的所有类 但是 如果 jar 已经在运行 我找不到附加到其 JVM 并与其类通信或获取其的方法env
  • 这些用简单的java代码创建的JVM守护线程是什么?

    我有一个非常简单的java应用程序 它只是创建一个对象 调用它的一个函数 所有这些都在一个无限循环中 public class h public static void main String args while true B b new

随机推荐