5

如何优雅的关闭JVM?

 3 years ago
source link: http://virtual.51cto.com/art/202101/641698.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

BJ3URn.jpg!mobile

前言

1、基本概述

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

2、JVM关闭的场景分类

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

a2EFRnM.jpg!mobile

从上面可以看到,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,先写到这。相关的经验方法,并推荐几本关于体验、思考的书籍。希望对同学们有所启发。

本文转载自微信公众号「愚公要移山」,可以通过以下二维码关注。转载本文请联系愚公要移山公众号。

FbYFNvf.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK