JVM Shutdown Hook

须弥零一

共 6806字,需浏览 14分钟

 ·

2023-06-14 13:34

须弥零一JVM Shutdown Hook

今天偶然看到 java.lang.Runtime 类的一个方法 public void addShutdownHook(Thread hook) 。

它的 javadoc 是这么写的:

Registers a new virtual-machine shutdown hook.
The Java virtual machine shuts down in response to two kinds of events:

  • • The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or

  • • The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.

意思就是说可以给 JVM 注册一个钩子,这个钩子将在虚拟机关闭的执行。当然这个 关闭 是有条件的。

写个例子

    
    
      public
       
      class
       
      ShutdownHookTest
       
      {
      

     public   static   void   main ( String []  args )   {
         System . out . println ( "Main thread start" );
         Thread   hook   =   new   Thread ( new   MyShutdownHook ());
         Runtime . getRuntime (). addShutdownHook ( hook );
         System . out . println ( "Main thread end" );
     }

     static   class   MyShutdownHook   implements   Runnable   {
         @Override
         public   void   run ()   {
             System . out . println ( "-- my shutdown hook start --" );
             try   {
                 System . out . println ( "-- do hook task --" );
                 Thread . sleep ( 5   *   1000L );
                 System . out . println ( "-- hook task finished --" );
             }   catch   ( InterruptedException  e )   {
                e . printStackTrace ();
             }
             System . out . println ( "-- my shutdown hook end --" );
         }
     }

}

使用 javac 编译后运行,得到的输出结果是:

    
    
      Main
       thread start
      
Main  thread  end
--   my  shutdown hook start  --
--   do  hook task  --
--  hook task finished  --
--   my  shutdown hook  end   --

结果符合文章刚开始引文的第一个情况,当程序的正常退出时会执行注册的钩子。也就是说,在程序主线程(实际上是所有的 demon线程)结束后,会启动执行钩子线程。

程序非执行完成推出的例子

稍微改一下上面的代码:

    
    
      public
       
      class
       
      ShutdownHookTest
       
      {
      

     public   static   void   main ( String []  args )   throws   Exception {
         System . out . println ( "Main thread start" );
         Thread   hook   =   new   Thread ( new   MyShutdownHook ());
         Runtime . getRuntime (). addShutdownHook ( hook );
         // --- 改了这里 ---
         Thread . sleep ( 120   *   1000L );
         // --------------
         System . out . println ( "Main thread end" );
     }

     static   class   MyShutdownHook   implements   Runnable   {
         @Override
         public   void   run ()   {
             System . out . println ( "-- my shutdown hook start --" );
             try   {
                 System . out . println ( "-- do hook task --" );
                 Thread . sleep ( 5   *   1000L );
                 System . out . println ( "-- hook task finished --" );
             }   catch   ( InterruptedException  e )   {
                e . printStackTrace ();
             }
             System . out . println ( "-- my shutdown hook end --" );
         }
     }

}

同样使用 javac 编译后运行。不同的是,在程序启动后按 ctrl + c 停止程序,将得到下面的输出:

    
    
      Main
       thread start
      
--   my  shutdown hook start  --
--   do  hook task  --
--  hook task finished  --
--   my  shutdown hook  end   --

这个结果也符合文章开始引文的第二个情况。当虚拟机用为用户输入 ^C 时,虚拟机会调用已注册的钩子。

另外因为也提到了当用户注销系统关闭时也会调用已注册的钩子,这里就不做验证了。

钩子不能执行的情况

同样是上面的例子。程序在启动后,打开任务管理器(Windows),找到对应的进程并结束。这时控制台的输出为:

    
    
      Main
       thread start
    
  

从输出可以看到,钩子并没有执行。

这就说明,在虚拟机中止 (注意:这里的中止不同于退出或停止,是指异常的break) 的情况下,钩子不会被调用执行。

javadoc 也给出了这种情况的说明:

在极少数虚拟机被外部中止的情况下,例如:

  • • 在 Unix 上使用 SIGKILL 信号

  • • 在 Windows 上使用 TerminateProcess 调用

  • • 执行本地方法出错

也就是说,虚拟机在没有干净地关闭的情况下停止运行,虚拟机则不能保证是否正确的运行关机钩子。

移除钩子

移除钩子使用方法 public boolean removeShutdownHook(Thread hook) 即可。

使用场景

看到这个特性,第一个想到的场景就是。程序在关闭时可以记录一个日志或发送一个通知。

当然,这个基于这个特性,可以定制出来很多使用场景。

但是,鉴于这个钩子的执行时机,就有很多需要注意的地方:

  • • 关机钩子(shutdown hook)必须是一个已初始化但未启动的线程

  • • 如果注册了多个钩子,则不能保证这些钩子的执行顺序,他们是同时开始的

  • • 当虚拟机关机序列开始,则无法在注册或取消钩子

  • • 对于钩子的线程的编写,应该是线程安全的,并尽可能地避免出现死锁

  • • 钩子的执行时间不应过长,也不应该添加任何用户交互功能

    • • 这时因为当用户注销或者关机时,底层操作系统可能只允许有限的固定时间来关闭和退出虚拟机

最后

文章就写到这里。

这篇文章没啥深入的探究,只是突然发现了一个之前未曾注意到的功能,简单的做一下记录和测试。

大家如果要在生产环境中使用,要是场景复杂还是慎重些,上一章节的那些注意事项都需要考虑考虑。

---- END ----

欢迎关注我的公众号“须弥零一”,原创技术文章第一时间推送。

浏览 47
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报