百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

如何优雅地停止Java进程_如何优雅地停止java进程运行

sinye56 2025-02-18 13:13 5 浏览 0 评论

目录

  • 理解停止Java进程的本质
  • 应该如何正确地停止Java进程如何注册关闭钩子使用关闭钩子的注意事项信号量机制
  • 总结

理解停止Java进程的本质

我们知道,Java程序的运行需要一个运行时环境,即:JVM,启动Java进程即启动了一个JVM。
因此,所谓停止Java进程,本质上就是关闭JVM。
那么,哪些情况会导致JVM关闭呢?

应该如何正确地停止Java进程

通常来讲,停止一个进程只需要杀死进程即可。
但是,在某些情况下可能需要在JVM关闭之前执行一些数据保存或者资源释放的工作,此时就不能直接强制杀死Java进程。

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

综上所述:

  1. 除非非常确定不需要在Java进程退出之前执行收尾的工作,否则强烈不建议使用kill -9这种简单暴力的方式强制停止Java进程(除了系统关机系统Crash断电,和Runtime.halt()我们无能为力之外)。
  2. 不论如何,都应该在Java进程中注册关闭钩子,尽最大可能地保证在Java进程退出之前做一些善后的事情(实际上,大多数时候都需要这样做)。

如何注册关闭钩子

在Java中注册关闭钩子通过Runtime类实现:

Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run() {
        // 在JVM关闭之前执行收尾工作
        // 注意事项:
        // 1.在这里执行的动作不能耗时太久
        // 2.不能在这里再执行注册,移除关闭钩子的操作
        // 3 不能在这里调用System.exit()
        System.out.println("do shutdown hook");
    }
});

为JVM注册关闭钩子的时机不固定,可以在启动Java进程之前,也可以在Java进程之后(如:在监听到操作系统信号量之后再注册关闭钩子也是可以的)。

使用关闭钩子的注意事项

1.关闭钩子本质上是一个线程(也称为Hook线程),对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不保证它们的执行顺序;由于是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议只注册一个钩子并在其中执行一系列操作。
2.Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间,避免hook线程中出现耗时的计算、等待用户I/O等等操作。
3.关闭钩子执行过程中可能被强制打断,比如在操作系统关机时,操作系统会等待进程停止,等待超时,进程仍未停止,操作系统会强制的杀死该进程,在这类情况下,关闭钩子在执行过程中被强制中止。
4.在关闭钩子中,不能执行注册、移除钩子的操作,JVM将关闭钩子序列初始化完毕后,不允许再次添加或者移除已经存在的钩子,否则JVM抛出IllegalStateException异常。
5.不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()。
6.Hook线程中同样会抛出异常,对于未捕捉的异常,线程的默认异常处理器处理该异常(将异常信息打印到System.err),不会影响其他hook线程以及JVM正常退出。

信号量机制

注册关闭钩子的目的是为了在JVM关闭之前执行一些收尾的动作,而从上述描述可以知道,触发关闭钩子动作的执行需要满足JVM正常关闭或异常关闭的情形。
显然,我们应该正常关闭JVM(异常关闭JVM的情形不希望发生,也无法百分之百地完全杜绝),即执行:System.exit()Ctrl + Ckill -15 进程ID

  • System.exit():通常我们在程序运行完毕之后调用,这是在应用代码中写死的,无法在进程外部进行调用。
  • Ctrl + C:如果Java进程运行在操作系统前台,可以通过键盘中断的方式结束运行;但是当进程在后台运行时,就无法通过Ctrl + C方式退出了。
  • Kill (-15)SIGTERM信号:使用kill命令结束进程是使用操作系统的信号量机制,不论进程运行在操作系统前台还是后台,都可以通过kill命令结束进程,这也是结束进程使用得最多的方式。

实际上,大多数情况下的进程结束操作通常是在进程运行过程中需要停止进程或者重启进程,而不是等待进程自己运行结束(服务程序都是一直运行的,并不会主动结束)。也就是说,针对JVM正常关闭的情形,大多数情况是使用kill -15 进程ID的方式实现的。那么,我们是否可以结合操作系统的信号量机制和JVM的关闭钩子实现优雅地关闭Java进程呢?答案是肯定的,具体实现步骤如下:

第一步:在应用程序中监听信号量
由于不通的操作系统类型实现的信号量动作存在差异,所以监听的信号量需要根据Java进程实际运行的环境而定(如:Windows使用SIGINT,Linux使用SIGTERM)。

Signal sg = new Signal("TERM"); // kill -15 pid
Signal.handle(sg, new SignalHandler() {
    @Override
    public void handle(Signal signal) {
        System.out.println("signal handle: " + signal.getName());
        // 监听信号量,通过System.exit(0)正常关闭JVM,触发关闭钩子执行收尾工作
        System.exit(0);
    }
});

第二步:注册关闭钩子

Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run() {
        // 执行进程退出前的工作
        // 注意事项:
        // 1.在这里执行的动作不能耗时太久
        // 2.不能在这里再执行注册,移除关闭钩子的操作
        // 3 不能在这里调用System.exit()
        System.out.println("do something");
    }
});

完整示例如下:

public class ShutdownTest {
    public static void main(String[] args) {
        System.out.println("Shutdown Test");

        Signal sg = new Signal("TERM"); // kill -15 pid
        // 监听信号量
        Signal.handle(sg, new SignalHandler() {
            @Override
            public void handle(Signal signal) {
                System.out.println("signal handle: " + signal.getName());
                System.exit(0);
            }
        });
        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                // 在关闭钩子中执行收尾工作
                // 注意事项:
                // 1.在这里执行的动作不能耗时太久
                // 2.不能在这里再执行注册,移除关闭钩子的操作
                // 3 不能在这里调用System.exit()
                System.out.println("do shutdown hook");
            }
        });

        mockWork();

        System.out.println("Done.");
        System.exit(0);
    }

    // 模拟进程正在运行
    private static void mockWork() {
        //mockRuntimeException();
        //mockOOM();
        try {
            Thread.sleep(120 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 
    }

    // 模拟在应用中抛出RuntimeException时会调用注册钩子
    private static void mockRuntimeException() {
        throw new RuntimeException("This is a mock runtime ex");
    }

    // 模拟应用运行出现OOM时会调用注册钩子
    // -xms10m -xmx10m
    private static void mockOOM() {
        List list = new ArrayList();
        for(int i = 0; i < 1000000; i++) {
            list.add(new Object());
        }
    }
}

总结

网上有文章总结说可以直接使用监听信号量的机制来实现优雅地关闭Java进程(详见:Java程序优雅关闭的两种方法),实际上这是有问题的。因为单纯地监听信号量,并不能覆盖到异常关闭JVM的情形(如:RuntimeException或OOM),这种方式与注册关闭钩子的区别在于:
1.关闭钩子是在独立线程中运行的,当应用进程被kill的时候main函数就已经结束了,仅会运行ShutdownHook线程中run()方法的代码。
2.监听信号量方法中handle函数会在进程被kill时收到TERM信号,但对main函数的运行不会有任何影响,需要使用别的方式结束main函数(如:在main函数中添加布尔类型的flag,当收到TERM信号时修改该flag,程序便会正常结束;或者在handle函数中调用System.exit())。

【参考】
https://blog.csdn.net/u011001084/article/details/73480432 JVM安全退出(如何优雅的关闭java服务)
http://yuanke52014.iteye.com/blog/2306805 Java保证程序结束时调用释放资源函数
https://tessykandy.iteye.com/blog/2005767 基于kill信号优雅的关闭JAVA程序
https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html Linux 信号signal处理机制

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: