如何优雅地关闭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,先写到这。

目录
打赏
0
0
0
0
26
分享
相关文章
JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
161 0
Java基础内容之JVM钩子hooks函数
请问在Spring中,如果JVM异常终止,Spring是如何保证会释放掉占用的资源,比如说数据库连接等资源呢? 钩子函数非常简单,简单到小编只用摘抄一段Spring代码即可。走你,现在开始。
395 0
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
622 166
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
742 1
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
|
5月前
|
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
58 4
|
16天前
|
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
19 3
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
4月前
|
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等