如何优雅地关闭JVM?看看钩子函数如何一步实现

简介: 程序的启动很简单,启动的时候通常会做一些预加载资源的操作。但是有时候关闭的时候,启动的时候预加载的资源并没有完全清理干净,因此可以使用钩子函数来完成。

前言


1、基本概述


程序的启动很简单,启动的时候通常会做一些预加载资源的操作。但是有时候关闭的时候,启动的时候预加载的资源并没有完全清理干净,因此可以使用钩子函数来完成。


2、JVM关闭的场景分类


直接看一张图吧,本图来自博客园的BarryWang,特在此说明。

v2-78946ec3c49880217596b7ca9359e074_1440w.jpg

从上面可以看到,JVM关闭主要分为了三类,第一种是正常的关闭,第二种是异常关闭的情况,第三种是强制关闭的情况。对于前两种方式我们可以使用钩子函数优雅的关闭,但是强制关闭的时候钩子函数并不起作用。


有了这些概念,我们直接使用一个案例进行演示,再进行分析。


一、代码演示钩子函数


1、JVM正常关闭


直接看代码吧,

public class Test {
 public void start(){
  Runtime.getRuntime().addShutdownHook(new Thread(()-> 
    System.out.println("钩子函数被执行,可以在这里关闭资源")
  ));
 }
 public static void main(String[] args) throws Exception{
  new Test().start();
  System.out.println("主应用程序在执行");
 }
}
//控制台输出
//主应用程序在执行
//钩子函数被执行,可以在这里关闭资源

看控制台打印,可以发现,主应用程序执行完之后就会调用钩子函数,接下来就会正式的关闭JVM。


2、异常关闭


还是直接看代码演示,这里我们演示异常关闭的第二种OOM的情况,我们可以先设置堆的大小为20M,然后在代码中创建一个500M的对象,这样就会OOM。参数是-Xmx20M


public class Test {
 public void start(){
  Runtime.getRuntime().addShutdownHook(new Thread(()-> 
    System.out.println("钩子函数被执行,可以在这里关闭资源")
  ));
 }
 public static void main(String[] args) throws Exception{
  new Test().start();
  System.out.println("主应用程序在执行");
  Runtime.getRuntime().halt(1);
  byte[] b = new byte[500*1024*1024];
 }
}
//控制台输出
//主应用程序在执行
//钩子函数被执行,可以在这里关闭资源

从控制台可以看出,钩子函数在异常关闭的时候依然会被调用。


3、强制关闭


这里我们使用Runtime.getRuntime().halt()来演示强势关闭。这个方法和System.exit的区别是,System.exit会执行钩子函数,但是Runtime.getRuntime().halt()不会。

public class Test {
 public void start(){
  Runtime.getRuntime().addShutdownHook(new Thread(()-> 
    System.out.println("钩子函数被执行,可以在这里关闭资源")
  ));
 }
 public static void main(String[] args) throws Exception{
  new Test().start();
  System.out.println("主应用程序在执行");
  Runtime.getRuntime().halt(1);
 }
}
//控制台输出
//主应用程序在执行

从上面代码的输出可以看出,调用了Runtime.getRuntime().halt(1)就会强制关闭JVM,钩子函数来不及执行就关闭了。而使用System.exit依然会执行。所以一般使用System.exit来关闭JVM。


4、移除钩子函数


上面演示了钩子函数的作用,有时候我们想移除也比较简单。

public class Test {
 public static void main(String[] args) throws Exception{
  //new Test().start();
  Thread willNotRun = new Thread(() -> 
   System.out.println("Won't run!"));
  Runtime.getRuntime().addShutdownHook(willNotRun);
  System.out.println("主应用程序在执行");
  Runtime.getRuntime().removeShutdownHook(willNotRun);
 }
}
//控制台输出
//主应用程序在执行

OK,钩子函数的基本操作就写到这,使用起来比较简单,不过我之前看过Spring的启动流程,所以又去那个启动流程看了一波,发现也使用到了钩子函数。


二、典型应用场景


1、Spring使用


Spring在关闭上下文的时候,可以使用钩子函数来关闭残留的资源。方法是使用ApplicationContext注册一个钩子函数即可。

ApplicationContext.registerShutdownHook();
//上面的这句代码可以分析进去看看
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
      this.shutdownHook = new Thread() {
        @Override
        public void run() {
          //Spring正常关闭
          doClose();
        }
      };
      //调用钩子函数关闭残留资源
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

从源码可以看出,Spring其实也是调用了Java的钩子函数进行关闭的。


2、其他使用


我在很多博客中也看到了spark和hadoop的关闭,由于我没看过源码,所以这里我说一下结论,对于其他的使用场景,基本上也是调用了Java的钩子函数来执行的。


结论


在关闭JVM的时候,我们可以封装钩子函数去优雅的关闭线程。不过在使用的时候还需要注意以下几个方面:


1、钩子函数本质是个线程


多个钩子会并发执行,JVM并不保证它们的执行顺序;因此最好是在一个钩子中执行一系列操作。


2、钩子中不能再新建钩子


在关闭钩子中,不能执行注册、移除钩子的操作,否则JVM抛出 IllegalStateException。也不能使用System.exit(),前面提到System.exit()会触发钩子函数的执行,但是Runtime.halt()可以,因为Runtime.halt()可以强制关闭。


3、钩子里最好不要有耗时操作


钩子函数主要用于关闭残留资源,因此不要有一些耗时的操作。

OK,先写到这。

相关文章
|
Java Linux iOS开发
JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
143 0
|
Java 数据库连接 Spring
Java基础内容之JVM钩子hooks函数
请问在Spring中,如果JVM异常终止,Spring是如何保证会释放掉占用的资源,比如说数据库连接等资源呢? 钩子函数非常简单,简单到小编只用摘抄一段Spring代码即可。走你,现在开始。
352 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
3月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
4天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
7 1
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
60 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
23天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
45 10
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。