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

这篇具有很好参考价值的文章主要介绍了JVM安全退出(如何优雅的关闭java服务)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

JVM 关闭

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

JVM安全退出(如何优雅的关闭java服务),Java细节,安全,Java,JVM,线程

对于强制关闭的几种情况,系统关机,操作系统会通知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安全退出(如何优雅的关闭java服务),Java细节,安全,Java,JVM,线程

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

JVM安全退出(如何优雅的关闭java服务),Java细节,安全,Java,JVM,线程

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

Signal sig = new Signal(getOSSignalType());

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

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

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接口,代码示例如下:

Signal.handle(sig, shutdownHandler);

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

public class ShutdownHandler implements SignalHandler {
    /**
     * 处理信号
     *
     * @param signal 信号
     */
    public void handle(Signal signal) {
    }
}

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

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

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

Runtime.getRuntime().exit(0);

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

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关闭前,执行应用程序的一些扫尾工作,进一步保证应用程序可以安全的退出。文章来源地址https://www.toymoban.com/news/detail-842308.html

到了这里,关于JVM安全退出(如何优雅的关闭java服务)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java如何优雅的进行判空

    实际项目中我们会有很多地方需要判空校验,如果不做判空校验则可能产生NullPointerException异常。 针对异常的处理我们在上一篇有提及: 先来看一下实际项目中一些判空方式 通常我们判断一个对象是否为Null,可以使用java.util中的Objects.nonNull(obj)、hutool中的ObjectUtil或者直接

    2024年02月09日
    浏览(63)
  • 代码优雅之道——Java如何判空?

    实际项目中我们会有很多地方需要判空校验,如果不做判空校验则可能产生NullPointerException异常。 针对异常的处理我们在上一篇有提及: 先来看一下实际项目中一些判空方式 通常我们判断一个对象是否为Null,可以使用java.util中的Objects.nonNull(obj)、hutool中的ObjectUtil或者直接

    2024年02月15日
    浏览(79)
  • python多线程编程:如何优雅地关闭线程

    在并发编程中,我们可能会创建新线程,并在其中运行任务,可能由于一些原因,决定停止该线程。例如: 不再需要线程任务的结果了。 应用程序正在关闭。 线程执行可能已经出现了异常 关于python多线程编程知识,请参阅由浅入深掌握Python多线程编程 Threading 模块的 Threa

    2023年04月18日
    浏览(39)
  • 如何在 容器中 优雅的 运行 java 应用

    当需要运行Java应用时,不管在机器上还是容器里,一般的做法是运行 java 命令。 java -jar /opt/app.jar 即可 如果是多多jar 文件的只需用到 java -cp lib/* com.example.Main 定类路径(classpath)和入口 Java 类即可。 但 在构建容器镜像时,一般使用 Dockerfile 的 ENTRYPOINT 来指定运行的 java 命

    2024年02月15日
    浏览(39)
  • Java如何优雅地判断对象是否为空

    点击下载《Java如何优雅地判断对象是否为空》 在实际项目中,我们经常需要对各种变量或对象进行判空校验。这是因为,如果不进行判空校验,当遇到空值时,可能会导致程序出现NullPointerException异常。这是一种常见的运行时异常,它会在试图访问或操作空对象引用时发生。

    2024年04月28日
    浏览(97)
  • 技术扫盲:如何优雅的使用 java -jar

    java -jar xxx.jar java -jar 是一个用于在命令行界面中执行 Java 可执行 JAR 文件的命令。它的语法如下: 其中: java 是 Java 运行时环境的可执行文件。 -jar 是一个选项,表示要执行的文件是一个 JAR 文件。 JAR 文件路径 是要执行的 JAR 文件的路径。 [参数] 是可选的命令行参数,用于

    2024年02月03日
    浏览(36)
  • java webSocket服务端、客户端、心跳检测优雅解决

    项目分为三个端,项目之间需要webSocket通信。 WebSocketConfig WebSocketServer

    2024年02月17日
    浏览(40)
  • Java中如何优雅的把Map转为对象

    在项目开发中,经常碰到map转实体对象或者对象转map的场景,工作中,很多时候我们可能比较喜欢使用第三方jar包的API对他们进行转化,而且用起来也还算方便,比如像fastJson就可以轻松实现map和对象的互转,但这里,我想通过反射的方式对他们做转化,也算是对反射的学习

    2024年02月13日
    浏览(38)
  • Linux exit命令教程:如何优雅地退出你的Shell(附案例详解和注意事项)

    Linux的exit命令用于退出当前运行的shell。它可以接受一个参数[N],并以状态N退出shell。如果没有提供n,则它只返回最后执行的命令的状态。 exit命令是内置在所有Linux发行版中的,包括但不限于Ubuntu、Debian、Fedora、RHEL、SUSE、Arch等。因此,无论你使用哪个Linux发行版,你都可以

    2024年02月04日
    浏览(51)
  • Java 如何关闭ssl 验证

    如果你希望在 Java 中关闭 SSL 验证,你可以使用以下方法: 使用自定义的 TrustManager。 在 SSLContext 中使用自定义的 TrustManager。 使用 HttpsURLConnection 时设置忽略证书。 下面是一些示例代码: 第一种方法:

    2024年02月13日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包