很多时候应用服务启动或关闭会做一些预加载(比如缓存,定时任务启动等)或收尾处理工作(比如程序失败记录等)

1. 首先看下Spring框架服务启动加载操作实现,直接上代码

继承实现接口ApplicationListener就可以实现:
import com.today.service.financereport.action.ExportReportRecordFailureAction
import com.today.service.financereport.common.ReportThreadManager
import com.today.service.financereport.dto.ExportReportFailureInput
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationListener
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.stereotype.Service /**
* 类功能描述:容器启动监听器
*
* @author WangXueXing create at 18-11-20 上午9:35
* @version 1.0.0
*/
@Service
class ContainerStartListener extends ApplicationListener[ContextRefreshedEvent] {
private val logger = LoggerFactory.getLogger(getClass)
override def onApplicationEvent(event: ContextRefreshedEvent): Unit = {
logger.info("容器正在启动...")
Runtime.getRuntime().addShutdownHook(new Thread(() => {
logger.info("容器将要关闭,关闭前处理开始...")
//1. 设置容器关闭前还未生成报表设置为导出失败
ReportThreadManager.REPORT_THREAD_MAP.keySet().forEach { x =>
new ExportReportRecordFailureAction(ExportReportFailureInput(x, new Throwable("容器被关闭"))).execute
}
logger.info("容器将要关闭,关闭前处理完成")
}))
}
}

2. 退出服务及几种退出方法

如下图:

对于强制关闭的几种情况,系统关机,操作系统会通知JVM进程关闭并等待,一旦等待超时,系统会强制中止JVM进程;kill -9、Runtime.halt()、断电、系统crash这些种方式会直接无商量中止JVM进程,JVM完全没有执行扫尾工作的机会。因此对用应用程序而言,我们强烈不建议使用kill -9 这种暴力方式退出。
而对于正常关闭、异常关闭的几种情况,JVM关闭前,都会调用已注册的shutdown hooks,基于这种机制,我们可以将扫尾的工作放在shutdown hooks中,进而使我们的应用程序安全的退出。基于平台通用性的考虑,我们更推荐应用程序使用System.exit(0)这种方式退出JVM。

JVM 与 shutdown hooks 交互流程如下图所示,可以对照源码进一步的学习shutdown hooks工作原理。

Jvm安全退出

对于tomcat类Web应用,我们可以直接通过Runtime.addShutdownHook(Thread hook)注册自定义钩子,在钩子中实现资源的清理;而对于worker类应用,我们可以采用如下的方式安全的退出应用。

基于信号的进程通知机制

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。通俗来讲,信号就是进程间的一种异步通信机制。信号具有平台相关性,Linux平台支持的一些终止进程信号如下所示:

信号名称 用途
SIGKILL 终止进程,强制杀死进程
SIGTERM 终止进程,软件终止信号
SIGTSTP 停止进程,终端来的停止信号
SIGPROF 终止进程,统计分布图用计时器到时
SIGUSR1 终止进程,用户定义信号1
SIGUSR2 终止进程,用户定义信号2
SIGINT 终止进程,中断进程
SIGQUIT 建立CORE文件终止进程,并且生成core文件

Windows平台存在一些差异,它的一些信号举例如下所示:

信号名称 用途
SIGINT Ctrl+C中断
SIGTERM kill发出的软件终止
SIGBREAK Ctrl+Break中断

信号选择:为了不干扰正常信号的运作,又能模拟Java异步通知,在Linux上我们需要先选定一种特殊的信号。通过查看信号列表上的描述,发现 SIGUSR1 和 SIGUSR2 是允许用户自定义的信号,我们可以选择SIGUSR2,在Windows上我们可以选择SIGINT。

通过这种信号机制,对应用程序JVM发送特定信号,JVM可以感知并处理该信号,进而可以接受程序退出指令。

安全退出实现

首先看下通用的JVM安全退出的流程图:

第一步,应用进程启动的时候,初始化Signal实例,它的代码示例如下:

1
Signal sig = new Signal(getOSSignalType());

其中Signal构造函数的参数为String字符串,也就上文介绍的信号量名称。

第二步,根据操作系统的名称来获取对应的信号名称,代码如下:

1
2
3
4
5
private String getOSSignalType()
{
return System.getProperties().getProperty("os.name").
toLowerCase().startsWith("win") ? "INT" : "USR2";
}

判断是否是windows操作系统,如果是则选择SIGINT,接收Ctrl+C中断的指令;否则选择USR2信号,接收SIGUSR2(等价于kill -12 pid)指令。

第三步,将实例化之后的SignalHandler注册到JVM的Signal,一旦JVM进程接收到kill -12 或者 Ctrl+C则回调handle接口,代码示例如下:

1
Signal.handle(sig, shutdownHandler);

其中shutdownHandler实现了SignalHandler接口的handle(Signal sgin)方法,代码示例如下:

1
2
3
4
5
6
7
8
9
public class ShutdownHandler implements SignalHandler {
/**
* 处理信号
*
* @param signal 信号
*/
public void handle(Signal signal) {
}
}

第四步,在接收到信号回调的handle接口中,初始化JVM的ShutdownHook线程,并将其注册到Runtime中,示例代码如下:

1
2
3
4
5
private void registerShutdownHook()
{
Thread t = new Thread(new ShutdownHook(), "ShutdownHook-Thread");
Runtime.getRuntime().addShutdownHook(t);
}

第五步,接收到进程退出信号后,在回调的handle接口中执行虚拟机的退出操作,示例代码如下:

1
Runtime.getRuntime().exit(0);

JVM退出时,底层会自动检测用户是否注册了ShutdownHook任务,如果有,则会自动执行注册钩子的Run方法,应用只需要在ShutdownHook中执行扫尾工作即可,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ShutdownHook implements Runnable
{
@Override
public void run() {
System.out.println("ShutdownHook execute start...");
try {
TimeUnit.SECONDS.sleep(10);//模拟应用进程退出前的处理操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ShutdownHook execute end...");
}
}

通过以上的几个步骤,我们可以轻松实现JVM的安全退出,另外,通常安全退出需要有超时控制机制,例如30S,如果到达超时时间仍然没有完成退出,则由停机脚本直接调用kill -9强制退出。

使用关闭钩子的注意事项

  • 关闭钩子本质上是一个线程(也称为Hook线程),对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不保证它们的执行顺序;由于是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。

  • Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间,避免hook线程中出现耗时的计算、等待用户I/O等等操作。

  • 关闭钩子执行过程中可能被强制打断,比如在操作系统关机时,操作系统会等待进程停止,等待超时,进程仍未停止,操作系统会强制的杀死该进程,在这类情况下,关闭钩子在执行过程中被强制中止。
  • 在关闭钩子中,不能执行注册、移除钩子的操作,JVM将关闭钩子序列初始化完毕后,不允许再次添加或者移除已经存在的钩子,否则JVM抛出 IllegalStateException。
  • 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()。
  • Hook线程中同样会抛出异常,对于未捕捉的异常,线程的默认异常处理器处理该异常,不会影响其他hook线程以及JVM正常退出。

总结

为了保障应用重启过程中异步操作的执行,避免强制退出JVM可能产生的各种问题,我们可以采用关闭钩子、自定义信号的方式,主动的通知JVM退出,并在JVM关闭前,执行应用程序的一些扫尾工作,进一步保证应用程序可以安全的退出。

Jvm启动,关闭及对应钩子的更多相关文章

  1. JVM 启动参数及原理 转

    Java虚拟机(JVM)是Java应用的运行环境,从一般意义上来讲,JVM是通过规范来定义的一个虚拟的计算机,被设计用来解释执行从Java源码编译而来的字节码.更通俗地说,JVM是指对这个规范的具体实 ...

  2. JVM启动参数手册——JVM之八

    jdk1.4.2 JVM官方地址:http://java.sun.com/j2se/1.4.2/docs/guide/vm/index.html 标准和非标注参数(for windows):http: ...

  3. Java内存管理-初始JVM和JVM启动流程(二)

    勿在流沙住高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分享了什么是程序,以及Java程序运行的三个阶段.也顺便提到了Java中比较重要 ...

  4. JVM启动参数大全

    java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足, ...

  5. JVM 启动参数,共分为3类

    JVM 启动参数,共分为3类: 类别 说明 标准参数(-) 所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 非标准参数(-X) 这些参数不是虚拟机规范规定的.因此,不是所有VM的实现(如:H ...

  6. JVM启动参数大全及默认值

    Java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足, ...

  7. 启动/关闭oracle服务有三种方式

    启动oracle服务有三种方式: 1 从控制面板 2 使用MS-DOS命令 3 通过Oracle Administration Assistant for WindowsNT -通过控制面板启动ora ...

  8. Oracle 服务手动启动关闭

    在windows7中安装完Oracle11g之后会出现一下七种服务:可通过运行->services.msc查看. 其中各个服务名称中的ORCL或orcl为SID即System IDentifie ...

  9. NetBeansRCP-添加/修改NetBeans的JVM启动参数

    NetBeans运行的速度实在是不敢恭维.还好机器配置还可以,修改其JVM启动参数命令行,以期运行的更加顺畅. 那么如何修改NetBeans IDE的JVM参数呢? 1.到NetBeans IDE的安 ...

随机推荐

  1. python函数调用的四种方式 --基础重点

    第一种:参数按顺序从第一个参数往后排#标准调用 # -*- coding: UTF-8 -*- def normal_invoke(x, y): print "--normal_invoke ...

  2. 在JavaWeb项目中URL中字符串加密解密方案

    URL由来: 一般来说,URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和符号.比如,世界上有英文字母的网址 “http://www.abc.com”,但是没有希腊字母的网址“htt ...

  3. Using variables inside Postman and Collection Runner

    Variables are among the most powerful features in Postman. Using variables in your Postman requests, ...

  4. serialPort操作结构体Hashtable的使用

  5. 把一个机器上1天内新增的文件用rsync传送到另外一台机器

    我的shell很菜,只好用shell和php结合来做 1.查找新增的文件,构造rsync的参数,把结果写入到log中 find /data/bmob/test/teststorage/data/ -m ...

  6. jQuery的入口函数四种写法

    1.第一种: $(document).ready(function(){ }); 2.第二种(最简洁的写法,推荐): $(function(){ }); 3.第三种: jQuery(document) ...

  7. 「SDOI2018」物理实验

    题目大意: 这题不好描述,直接看原题吧…… 题解: 很无脑的题……就是卡精度+难写.代码能力还是太差了. 其实可以直接用long double肝过去.但我的代码似乎太丑了,以至于跑得奇慢无比. 代码: ...

  8. vue不是内部或外部命令解决验证方案

    一.前提 1.该教程是在你已经安装配置好node.js和express情况下 2.你已经完成了vue和vue-cli的全局安装 3.完成以上2步后,使用vue指令,会显示"vue不是内部或外 ...

  9. python——在文件存放路径下自动创建文件夹!

    1.a.py文件存放的路径下为(D:\Auto\eclipse\workspace\Testhtml\Test) 2.通过os.getcwd()获取的路径为:D:\Auto\eclipse\works ...

  10. 玩转PHP中的正则表达式

    玩转PHP中的正则表达式 检验用户输入.解析用户输入和文件内容,以及重新格式化字符串 级别: 中级 正则表达式提供了一种处理文本的强大方法.使用正则表达式,您可以对用户输入进行复杂的检验.解析用户输入 ...