JVM安全退出(如何优雅的关闭java服务)

2023-11-18

https://tech.imdada.cn/2017/06/18/jvm-safe-exit/?utm_source=tuicool&utm_medium=referral

背景

用户:货都到了,购物车里怎么还有刚买的东西,what?
产品:有用户反映,提单完成了,怎么没清购物车,研发赶紧看看是不是有bug啊?
研发:恩,我看看,!@#¥%……&*()一顿狂查,搜嘎,当时在上线,重启应用,异步任务丢了……
产品:能不能行,上线你就丢任务,丢不丢人啊!
研发:…………

上线!重启!你还在为丢失任务而烦恼么?看这里看这里,从此不再丢任务,JVM可以安全退出的

在交易流程中,为了提升服务的性能,我们做了一些异步化的优化,比如更新用户最近使用的收货地址、提单完成后通过MQ去发送各种通知类消息、清理用户的购物车等等这些操作,异步化加快了应用的响应速度同时也带来一个隐患,如何保障异步操作的执行?这个场景主要发生在应用重启时,对于通过线程或线程池进行的异步化,JVM重启时,后台执行的异步操作可能尚未完成。这时,需要通过JVM安全关闭来保证异步操作进行完成后,JVM再执行关闭。
更广泛的说,在Linux上很多应用通常会通过kill -9 pid的方式强制将进程杀掉,这种方式简单高效,因此很多应用的停止脚本经常会选择使用kill -9 pid的方式。强制进程退出,会带来一些副作用,对应用程序而言其效果等同于突然掉电,可能会导致如下一些问题:

  1. 缓存中的数据尚未持久化到磁盘中,导致数据丢失;
  2. 正在进行文件的write操作,没有更新完成,突然退出,导致文件损坏;
  3. 线程池的任务队列中尚有接收到的任务还没来得及处理,导致任务丢失;
  4. 数据库操作已经完成,例如账户余额更新,准备返回应答消息给客户端时,消息尚在通信线程的发送队列中排队等待发送,进程强制退出导致应答消息没有返回给客户端,客户端发起超时重试,会带来重复更新问题;
  5. 其它问题等…

这些问题都有可能对我们的业务产生影响,造成不必要的损失,为了避免这些问题,我们需要在JVM关闭时做些扫尾的工作,为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。本文探讨了利用关闭钩子的相关内容。

JVM 关闭

首先,我们了解下哪些情况会导致JVM关闭,如下图

image

对于强制关闭的几种情况,系统关机,操作系统会通知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工作原理。
image

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安全退出的流程图:

image

第一步,应用进程启动的时候,初始化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关闭前,执行应用程序的一些扫尾工作,进一步保证应用程序可以安全的退出。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM安全退出(如何优雅的关闭java服务) 的相关文章

  • Java 系统属性的范围

    在Java中我们使用System setProperty 方法来设置一些系统属性 根据本文 http blogs oracle com foo entry monitored system setproperty系统属性的使用有点棘手 Sy
  • 在最近的 JVM 中,不可见引用仍然是一个问题吗?

    我正在读书Java 平台性能 http java sun com docs books performance 1st edition html JPAppGC fm html 遗憾的是 自从我最初提出这个问题以来 该链接似乎已经从互联网上
  • Java VM 突然退出且没有明显原因

    我的 Java 程序突然退出 没有抛出任何异常 也没有正常完成 这是一个问题 我正在写一个程序来解决欧拉计划 http projecteuler net s 这就是我得到的 private static final int INITIAL
  • 增加堆大小后无法启动 Glassfish

    我想增加 Glassfish 的堆大小 为此 我知道我可以达到 4GB java Xmx4000M version java version 1 6 0 26 Java TM SE Runtime Environment build 1 6
  • 是否可以使 java.lang.invoke.MethodHandle 与直接调用一样快?

    我正在比较性能MethodHandle invoke以及直接静态方法调用 这是静态方法 public class IntSum public static int sum int a int b return a b 这是我的基准 Stat
  • Scala 泛型 - 为什么我无法在泛型类中创建参数化对象?

    我目前正在学习scala 为什么此代码不起作用 class GenClass T var d T var elems List T Nil def dosom x T var y new T y 我得到 错误 需要类类型 但找到了 T 代替
  • GDB可以杀死一个特定的线程吗?

    我正在运行一个应用程序 firefox 我想知道是否可以使用 GDB 附加到进程并杀死特定线程 有没有办法做到这一点 我知道此操作可能会使应用程序崩溃 EDIT 在此调试会话中 ps ax显示firefox pid是1328 gdb App
  • linux 使用超时(以毫秒为单位)杀死进程

    我想在Linux上指定时间过后强制终止程序 我发现linux中的 timeout util可以在指定时间后杀死程序 但它不接受毫秒 也就是说 timeout TIME PROGRAM 会在 TIME 过去后杀死 PROGRAM 其中 TIM
  • 最近有关于 JVM 的书吗? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 杀死 OpenCL 内核

    有没有办法通过 OpenCL API 终止正在运行的 OpenCL 内核 我在规范中没有找到任何内容 我能想到的唯一解决方案是 1 定期检查内核中主机希望内核停止时写入的标志 或 2 在单独的进程中运行内核并终止整个进程 我认为这两个都不是
  • Java 中的引用变量里面有什么?

    我们知道对象引用变量保存表示访问对象的方式的位 它不保存对象本身 但保存诸如指针或地址之类的东西 我正在阅读 Head First Java 第 2 版 一书 书中写道 第 3 章第 54 页 在 Java 中我们并不真正知道什么是 在引用
  • 使用请求和多处理时的奇怪问题

    请检查这个Python代码 usr bin env python import requests import multiprocessing from time import sleep time from requests import
  • JVM 是否会内联对象的实例变量和方法?

    假设我有一个非常紧密的内部循环 每次迭代都会访问和改变一个簿记对象 该对象存储有关算法的一些简单数据 并具有用于操作它的简单逻辑 簿记对象是私有的和最终的 并且它的所有方法都是私有的 最终的和 inline 下面是一个示例 Scala 语法
  • 为什么不在下一个 JVM 中删除类型擦除呢?

    Java 在 Java 5 中引入了泛型类型擦除 因此它们可以在旧版本的 Java 上运行 这是兼容性的权衡 我们已经失去了这种兼容性 1 https stackoverflow com questions 22610400 a progr
  • Scala 对大数的阶乘有时会崩溃,有时不会

    以下程序经过编译和测试 有时返回结果 有时充满屏幕 java lang StackOverflowError at scala BigInt apply BigInt scala 47 at scala BigInt equals BigI
  • Java GuardedString - 用于加密的随机密钥是否存储在 Java 堆内存中?如果不是,那么密钥保存在哪里?

    Oracle 的 org identityconnectors common security GuardedString 要转换为 GuardedString 的原始数据需要由 EncryptorImpl class 随机生成的加密密钥
  • 监控 Java 应用程序上的锁争用

    我正在尝试创建一个小基准 在 Groovy 中 以显示几个同步方法上的高线程争用 当监控自愿上下文切换时 应该会出现高争用 在 Linux 中 这可以通过 pidstat 来实现 程序如下 class Res private int n s
  • JVM内存段分配

    好吧 我有一个关于 JVM 内存段的问题 我知道每个 JVM 都会选择稍微不同地实现这一点 但这是一个总体概念 在所有 JVM 中应该保持相同 一个在运行时不使用虚拟机执行的标准C C 程序在运行时有四个内存段 代码 堆栈 堆 数据 所有这
  • 如何在Java中计算对象的数字年龄[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想知道Java中对象的年龄 当我们使用new关键字时 Java中用户定义的对象被创建 但是什么时候它会被销毁 是跨越JVM的perm
  • Java 类:匿名类、嵌套类、私有类

    有人能解释一下Java中匿名类 嵌套类和私有类之间的区别吗 我想知道与每个相关的运行时成本以及每个编译器的方法 这样我就可以掌握哪个最适合用于例如性能 编译器优化的潜力 内存使用以及其他 Java 编码人员的普遍可接受性 我所说的匿名类是指

随机推荐

  • 直联和间联的区别——直连和间连的区别

    直联和间联的区别 直联就是商户直接和银联对接 商户 银联 直联一般商户自己支付手续费 间联就是商户间接和银联对接 商户 第三方支付 银联 间联也要支付 但一般是有人帮你付 直连和间连的区别 直连 指第三方支付直接与商业银行做对接 如果是本行
  • 硬件基础知识

    SPI是串行外设接口 Serial Peripheral Interface 的缩写 是一种高速的 全双工 同步的通信总线 SCLK SCLK是一种有固定周期并与运行无关的信号量 CLK CLK是一种脉冲信号 TDNN 时延神经网络 它的两
  • UE4持续集成打包(Mac脚本自动化打包)

    主要通过RunUAT进行打包 win和mac均可以打包 本次打包实现在Mac环境下 使用 Engine Build BatchFiles RunUAT sh 参考命令格式 参考文献1 RunUAT BuildCookRun project
  • 一些优秀的开源轻量级TCP/IP协议栈

    以下是一些优秀的开源轻量级TCP IP协议栈 它们适用于嵌入式设备和其他资源受限的环境 lwIP lightweight IP lwIP 是一个非常流行的开源 TCP IP 协议栈 它专门为嵌入式系统设计 具有低内存占用和高效率的特点 lw
  • 【小程序】实现经典2048小游戏

    概述 经典小游戏2048 2048小游戏对于逻辑要求还是很有技术含量的 有兴趣的可以看看 详细 以前学习时写的小游戏2048 技术含量还是不错的 有兴趣的可以看看 2048已经封装好了 在主页面直接引入文件可以直接调用 演示图 调用wxml
  • 设计圆和圆柱体

    编写一个完整的Java Application 程序 包含类Circle Cylinder Main 具体要求如下 1 编写类Circle 表示圆形对象 包含以下成员 属性 radius 私有 double型 圆形半径 方法 Circle
  • Python3.X出现AttributeError: module 'urllib' has no attribute 'urlopen'错误

    研究用Python写爬虫 下载一个网页 报错代码如下 import urllib def getHtml url page urllib urlopen url html page read return html html getHtml
  • 导致事务@Transactional失效的5种场景

    一个程序中不可能没有事务 而 Spring 中 事务的实现方式分为两种 编程式事务和声明式事务 又因为编程式事务实现相对麻烦 而声明式事务实现极其简单 所以在日常项目中 我们都会使用声明式事务 Transactional 来实现事务 Tra
  • 英文学术论文写作——模式识别方向(笔记)

    文章目录 文章结构 英文写作tips Latex小技巧 英文学术论文写作经验几乎为0 在老师和师兄们的帮助下 学习到了如何撰写文章 仅限于模式识别方向的 文章结构 文章除去abstract acknowledgment以及reference
  • 深度学习目标检测综述学习

    目录 0 摘要 1 引言 2 背景 2 1 问题描述 2 2 目标检测中的关键挑战 3 数据集以及评价指标 3 1 数据集 1 PASCAL VOC 07 12 2 ILSVRC 3 MS COCO 4 Open Image 3 2 指标
  • vue一行代码实现富文本编辑器

    vue中我们可以使用tinymce第三方组件 第一 我们先将tinymce下载下来 下载链接 https pan baidu com s 15hvafdE7czBM9Wdu5sh9Ow 提取码 kv48 然后引入两个文件到我们项目中 第二部
  • 第十一届蓝桥杯 ——互质(gcd求最大公约数)

    gcd最大公约数 Rudy的博客 CSDN博客 gcdhttps blog csdn net xiaoyue article details 83239172 ops request misc 257B 2522request 255Fid
  • go语言exec包调用shell命令

    工程中需要用到ffmpeg 想直接用exec包调用shell命令 本来以为很简单 结果折腾了一下午 最后查到了解决方案 假如之前执行报错的语句为 cmd exec Command echo helloworld out err cmd Ou
  • 智能时代悄然到来刷脸支付逐渐成为潮流

    随着人脸识别 人工智能 物联网 大数据等前沿技术的迅速发展 智能时代已悄然到来 刷脸支付也逐渐成为一种潮流 如今 刷脸支付愈发常见 除了乘车刷脸 看病刷脸外 值机 安检 登机也都可以刷脸了 机场不用排长队 不用身份证 仅需一张脸即可登机的刷
  • rabbitmq web界面报错 Access refused

    赋予权限就好了 rabbitmqctl set permissions p 当前登录账户的账号
  • 态势感知与态势理解

    几个星期前 我与我的一个机构同事碰面 讨论了最新的备受瞩目的袭击事件 他向我提到了一个新词 态势理解 在USB提案中做了8个月的工作后 我对催吐流行语并不陌生 这个词立即引起了人们的注意 但是由于我一直在讨论几天 所以这个词本身正在赢得信誉
  • 【MLOps】第 2 章 : MLOps中的人

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 备战2023蓝桥国赛-传纸条

    题目描述 解析 这道题想了我好久 一开始我是想假如只走一条路线 从 1 1 走到 m n 这种问题该怎么解决呢 针对这种问题我是设了dp k i j 表示走了k步到达 i j 的好心程度之和的最大值 然后根据这个来写出转移方程来计算 后面就
  • Nginx 队列双向链表结构 ngx_quene_t

    队列链表结构 队列双向循环链表实现文件 文件 src core ngx queue h c 在 Nginx 的队列实现中 实质就是具有头节点的双向循环链表 这里的双向链表中的节点是没有数据区的 只有两个指向节点的指针 需注意的是队列链表的内
  • JVM安全退出(如何优雅的关闭java服务)

    https tech imdada cn 2017 06 18 jvm safe exit utm source tuicool utm medium referral 背景 用户 货都到了 购物车里怎么还有刚买的东西 what 产品 有用