Java 在 Linux 上的守护进程:如何优雅地终止和管理自启动程序

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在上期文章中,我们讨论了 IntelliJ IDEA 社区版在 Mac 上 Java 文件图标变为黄色的原因,并深入解析了项目配置问题和解决方案。通过正确设置 SDK 和源文件目录,开发者能够顺利进行项目开发和管理。在日常开发过程中,我们不仅会遇到开发工具配置的问题,还会处理 Java 程序在生产环境中的运行与维护,尤其是守护进程(Daemon Process)的管理和退出控制。

本期文章我们将转向实际的 Java 应用部署场景,探讨 如何在 Linux 环境下管理 Java 守护进程以及终止自启动程序。守护进程作为后台运行的程序,对于系统稳定性和应用的持续运行至关重要。我们将结合代码示例,展示如何在 Java 中编写守护进程,并讨论如何安全地终止这些进程。

摘要

本文将围绕 如何在 Java 中管理 Linux 上的守护进程 展开,尤其重点探讨如何优雅地 kill 自启动程序。通过源码解析、使用案例分享以及核心类方法介绍,帮助开发者了解如何在 Linux 上编写和管理 Java 守护进程,同时学习如何在不破坏系统或导致数据丢失的情况下安全终止这些进程。

概述

在 Linux 操作系统中,守护进程是指在后台运行的服务或应用程序。它们通常在系统启动时自动启动,并且会持续运行,除非被手动终止或因某些异常退出。Java 程序在 Linux 环境下同样可以以守护进程的形式运行,尤其是在需要长期运行的服务或任务(例如 Web 服务、数据处理任务)中,守护进程尤为重要。

然而,守护进程一旦启动,如何在不中断系统其他服务的情况下优雅地终止它们则是一个需要仔细考虑的问题。在 Linux 环境下,kill 命令是最常见的用于终止进程的方式,但如果不正确地使用,可能会导致进程意外中断或数据丢失。

守护进程与 Java 程序

什么是守护进程?

守护进程(Daemon Process)是指在操作系统后台运行的进程,通常没有直接的用户交互界面。它们在系统启动时启动,通常在后台处理服务请求、执行定时任务或者维护系统状态。

在 Java 中,可以通过两种方式将程序作为守护进程运行:

  1. 使用第三方工具(如 nohupsystemd)启动 Java 程序
  2. 编写 Java 代码,手动控制守护进程的生命周期

守护进程的启动和关闭需要严格的控制,以确保系统的稳定性和数据的安全性。对于自启动程序,特别是自动运行的守护进程,如何在需要时优雅地终止这些程序非常重要。

源码解析

1. 编写一个简单的 Java 守护进程

在 Java 中编写一个长期运行的守护进程通常包括以下几个步骤:

  • 启动一个后台线程处理主任务。
  • 使用控制机制来监听关闭信号。
  • 在程序退出前完成资源清理工作。

以下代码展示了如何编写一个简单的 Java 守护进程。

public class SimpleDaemonProcess {private volatile boolean running = true;public void start() {// 启动守护进程Thread daemonThread = new Thread(() -> {while (running) {try {System.out.println("Daemon process is running...");// 模拟任务处理Thread.sleep(3000);} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("Daemon process interrupted.");}}System.out.println("Daemon process stopped.");});daemonThread.setDaemon(true);daemonThread.start();}public void stop() {running = false;System.out.println("Stopping daemon process...");}public static void main(String[] args) throws Exception {SimpleDaemonProcess process = new SimpleDaemonProcess();process.start();// 模拟守护进程在后台运行Thread.sleep(10000);process.stop();}
}

代码解析:

如下是具体的代码解析,希望对大家有所帮助:

这段Java代码定义了一个名为 SimpleDaemonProcess 的类,它模拟了一个简单的守护进程。守护进程是一种在后台运行的线程,通常用于执行一些服务性任务,如垃圾回收、信号处理等。

下面是这段代码的详细解读:

  1. private volatile boolean running = true;:定义了一个 running 变量,用来控制守护进程的运行状态。volatile 关键字确保多线程环境下的可见性和有序性。

  2. public void start() { ... }:定义了一个 start 方法,用于启动守护进程。

  3. Thread daemonThread = new Thread(() -> { ... });:创建了一个新的 Thread 对象,它的任务是运行一个 lambda 表达式。

  4. while (running) { ... }:在 runningtrue 的情况下,线程会循环运行。

  5. System.out.println("Daemon process is running...");:打印出守护进程正在运行的信息。

  6. Thread.sleep(3000);:线程休眠3000毫秒(3秒),模拟任务处理。

  7. catch (InterruptedException e) { ... }:捕获 InterruptedException 异常。当线程在休眠时被中断,会抛出此异常。

  8. Thread.currentThread().interrupt();:重新设置当前线程的中断状态。

  9. System.out.println("Daemon process interrupted.");:打印出守护进程被中断的信息。

  10. daemonThread.setDaemon(true);:将新创建的线程设置为守护线程。

  11. daemonThread.start();:启动守护线程。

  12. public void stop() { ... }:定义了一个 stop 方法,用于停止守护进程。

  13. running = false;:将 running 变量设置为 false,这会导致守护线程循环结束。

  14. System.out.println("Stopping daemon process...");:打印出正在停止守护进程的信息。

  15. public static void main(String[] args) throws Exception { ... }:定义了程序的主入口点 main 方法。

  16. SimpleDaemonProcess process = new SimpleDaemonProcess();:创建了 SimpleDaemonProcess 类的一个实例。

  17. process.start();:调用 start 方法启动守护进程。

  18. Thread.sleep(10000);:主线程休眠10000毫秒(10秒),模拟守护进程在后台运行。

  19. process.stop();:调用 stop 方法停止守护进程。

总言之:我这个类 SimpleDaemonProcess 模拟了一个守护进程的启动和停止过程。守护进程在后台运行,执行周期性的任务,直到收到停止信号。通过设置 running 变量为 false,守护进程可以优雅地停止。在 main 方法中,程序启动守护进程,运行一段时间后停止它。

2. 守护进程自启动和管理

在 Linux 系统中,可以通过 systemdinit.dnohup 等工具让 Java 守护进程自启动。例如,使用 nohup 可以让 Java 程序在后台持续运行:

nohup java -jar your-application.jar > output.log 2>&1 &
  • nohup:确保进程在退出终端后仍能继续运行。
  • &:将进程放入后台执行。

守护进程的启动相对简单,然而,如何终止它们往往需要更细致的处理。

3. 通过 kill 命令优雅地终止守护进程

为了安全地终止一个守护进程,可以使用 kill 命令发送不同的信号给进程。最常用的信号包括:

  • SIGTERM(15):请求进程退出,进程可以捕获此信号并执行清理工作。
  • SIGKILL(9):强制终止进程,进程无法捕获此信号。

在 Linux 系统中,可以通过以下命令查找并终止守护进程:

ps -ef | grep your-application-name
kill -SIGTERM <pid>

为了优雅地终止守护进程,Java 程序可以通过监听关闭信号(如 SIGTERM)来完成清理工作。

public class SignalHandlerDaemon {public static void main(String[] args) {Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("Shutdown hook triggered. Cleaning up resources...");// 执行必要的清理工作}));while (true) {System.out.println("Daemon process running...");try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}

在这段代码中,addShutdownHook 方法用于捕获系统信号并在程序结束前执行清理工作。这种方式确保了即使通过 kill -SIGTERM 终止进程,Java 程序也能够优雅退出。

使用案例分享

案例1:Web 服务守护进程管理

小张开发了一个基于 Java 的 Web 服务,并将其部署在 Linux 服务器上作为守护进程运行。通过 systemd 服务配置文件,他确保服务器启动时,Web 服务能够自动启动。同时,为了能够在服务器维护时安全地终止该服务,他为守护进程添加了 SIGTERM 信号处理逻辑。

案例2:定时任务的守护进程

小李负责的项目需要定时从多个数据源收集数据并存储到数据库中。她通过编写一个 Java 守护进程来处理定时任务,并通过 nohup 启动它。每当服务器需要重启时,她会先通过 kill -SIGTERM 终止进程,以确保所有数据保存完毕后再关闭服务。

应用场景分析

适用场景:

  • 需要在后台长期运行的 Java 服务或任务。
  • 定时任务或数据处理程序,需要系统自启动并持续运行。
  • 希望能够优雅地关闭守护进程,避免数据丢失或任务中断。

不适用场景:

  • 非长期运行的任务,或无需后台运行的程序。
  • 不需要处理复杂关闭流程的应用场景,如简单的短期脚本任务。

优缺点分析

优点

  • Java 可以轻松编写并部署后台运行的守护进程。
  • 利用 kill -SIGTERM 等信号处理机制,守护进程可以在终止前完成资源清理,保证数据的完整性。
  • Java 的跨平台特性允许守护进程在各种操作系统上运行,并通过简单的命令进行管理。

缺点

  • 编写和管理守护进程需要考虑到各种信号处理和线程管理的细节,否则可能导致进程无法优雅终止。
  • 如果没有正确处理关闭信号,可能会造成数据丢失或进程强制终止带来的其他问题。

核心类方法介绍

Thread.setDaemon()

该方法用于将线程设置为守护线程,守护线程在没有其他非守护线程运行时会自动结束。

Runtime.getRuntime().addShutdownHook()

此方法允许注册一个关闭钩子,当 JVM 关闭时自动执行该钩子中的清理逻辑,用于确保进程优雅地关闭。

kill -SIGTERM

该命令用于向进程发送 SIGTERM

号,通知进程进行清理并退出,适合用于优雅终止后台守护进程。

测试用例

import org.junit.Test;
import static org.junit.Assert.*;public class DaemonProcessTest {@Testpublic void testDaemonThreadRunning() {SimpleDaemonProcess daemon = new SimpleDaemonProcess();daemon.start();assertTrue(daemon.isRunning());}@Testpublic void testGracefulShutdown() {SignalHandlerDaemon daemon = new SignalHandlerDaemon();daemon.start();daemon.stop();assertFalse(daemon.isRunning());}
}

这些测试用例确保守护进程能够正确启动并优雅关闭,确保业务逻辑的持续性和稳定性。

代码解析:

如下是具体的代码解析,希望对大家有所帮助:

这段Java代码定义了一个名为 DaemonProcessTest 的类,其中包含两个测试方法,用于测试守护进程(Daemon Process)的行为。

下面是这段代码的详细解读:

  1. import org.junit.Test;:导入了JUnit测试框架中的 Test 注解。

  2. import static org.junit.Assert.*;:导入了JUnit断言类的静态成员,允许在测试方法中使用 assertTrueassertFalse 等断言方法。

  3. public class DaemonProcessTest { ... }:定义了一个名为 DaemonProcessTest 的公共类。

  4. @Test public void testDaemonThreadRunning() { ... }:定义了一个名为 testDaemonThreadRunning 的测试方法,用于测试守护线程是否能够正常运行。

  5. SimpleDaemonProcess daemon = new SimpleDaemonProcess();:创建了 SimpleDaemonProcess 类的一个实例。这里假设 SimpleDaemonProcess 是一个实现了 Runnable 接口的类,并且有一个 start 方法用于启动守护线程。

  6. daemon.start();:调用 daemon 实例的 start 方法,这通常会启动一个新的线程来运行 Runnablerun 方法。

  7. assertTrue(daemon.isRunning());:使用 assertTrue 断言方法来检查 daemon 是否正在运行。这里假设 SimpleDaemonProcess 类有一个 isRunning 方法,返回一个布尔值表示线程是否正在运行。

  8. @Test public void testGracefulShutdown() { ... }:定义了另一个名为 testGracefulShutdown 的测试方法,用于测试守护进程是否能够优雅地关闭。

  9. SignalHandlerDaemon daemon = new SignalHandlerDaemon();:创建了 SignalHandlerDaemon 类的一个实例。这里假设 SignalHandlerDaemon 是一个类,它可能处理某种信号或事件。

  10. daemon.start();:调用 daemon 实例的 start 方法,启动守护进程。

  11. daemon.stop();:调用 daemon 实例的 stop 方法,请求停止守护进程。

  12. assertFalse(daemon.isRunning());:使用 assertFalse 断言方法来检查守护进程是否已经停止。这里假设 SignalHandlerDaemon 类有一个 isRunning 方法,返回一个布尔值表示进程是否正在运行。

总结:这个类 DaemonProcessTest 包含了两个测试方法,用于验证守护进程的启动和停止行为。第一个测试方法 testDaemonThreadRunning 确保守护线程在启动后是运行状态。第二个测试方法 testGracefulShutdown 确保守护进程能够响应停止请求并正确地关闭。

注意:代码中假设的 SimpleDaemonProcessSignalHandlerDaemon 类需要有 startstopisRunning 方法的实现,这些方法分别用于启动、停止进程和检查进程的运行状态。

小结

通过本文的介绍,我们深入了解了 Java 守护进程 的工作原理,特别是在 Linux 系统中的应用。我们展示了如何通过 nohup 等工具启动 Java 守护进程,以及如何优雅地通过 kill 命令终止自启动程序,确保进程能够安全地结束而不会导致数据丢失或系统不稳定。

总结

在 Linux 环境中,管理 Java 守护进程是开发和运维中一个重要且常见的任务。通过编写守护进程代码并使用 kill -SIGTERM 等命令,开发者可以实现守护进程的自启动和优雅关闭,确保系统的稳定运行。在实际应用中,理解守护进程的生命周期并善加利用系统提供的工具,将极大提升系统的健壮性和数据的安全性。

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有一台服务器可以做哪些很酷的事情

有一台服务器可以做哪些很酷的事情 今天我也来简单分享一下&#xff0c;这几年来&#xff0c;我用云服务器做了哪些有趣的事情。 服务器推荐 1. 个人博客 拥有个人服务器&#xff0c;你可以完全掌控自己的网站或博客。 与使用第三方托管平台相比&#xff0c;你能自由选择网站…

科研绘图系列:R语言绘制Y轴截断分组柱状图(y-axis break bar plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍特点意义加载R包数据下载导入数据数据预处理画图输出总结系统信息介绍 Y轴截断分组柱状图是一种特殊的柱状图,其特点是Y轴的刻度被截断,即在某个范围内省略了部分刻度。这种图表…

pytest+request+yaml+allure搭建低编码调试门槛的接口自动化框架

接口自动化非常简单&#xff0c;大致分为以下几步&#xff1a; 准备入参调用接口拿到2中response&#xff0c;继续组装入参&#xff0c;调用下一个接口重复步骤3校验结果是否符合预期 一个优秀接口自动化框架的特点&#xff1a; 【编码门槛低】&#xff0c;又【能让新手学到…

三轴云台之RTSP流分辨率

三轴云台是一种能够在三个轴向上&#xff08;通常是俯仰、偏航和滚动轴&#xff09;准确、稳定地控制其负载&#xff08;如相机、传感器等&#xff09;位置和姿态的设备。而RTSP&#xff08;Real Time Streaming Protocol&#xff09;是一种网络控制协议&#xff0c;用于控制媒…

Facebook 隐私变革之路:回顾与展望

在数字时代&#xff0c;个人隐私的保护一直是社交平台面临的重大挑战之一。作为全球最大的社交网络平台&#xff0c;Facebook&#xff08;现为Meta&#xff09;在处理用户隐私方面的变革&#xff0c;历经了多次调整与完善。本文将回顾Facebook在隐私保护方面的历程&#xff0c;…

STM32 USB组合设备 MSC CDC

STM32 USB组合设备 MSC CDC实现 教程 教程请看大佬niu_88 手把手教你使用USB的CDCMSC复合设备&#xff08;基于stm32f407&#xff09; 大佬的教程很好&#xff0c;很详细&#xff0c;我调出来了&#xff0c;代码请见我绑定的资源 注意事项 值得注意的是&#xff1a; 1、 cu…

【C盘清理】C盘清理工具、Unity缓存文件转移

链接: https://pan.baidu.com/s/1yE_7qF741o4NmBIsrd3XzA?pwdbwnn CCleaner 用于清理磁盘垃圾 勾选你要分析的选项&#xff0c;点击分析&#xff0c;分析完毕后&#xff0c;点击清理。 主要别清错东西了。&#xff08;可以不要勾选网络缓存、网络记录相关的选项&#xff0…

用户注册模块用户校验(头条项目-05)

1 用户注册后端逻辑 1.1 接收参数 username request.POST.get(username) password request.POST.get(password) phone request.POST.get(phone) 1.2 校验参数 前端校验过的后端也要校验&#xff0c;后端的校验和前端的校验是⼀致的 # 判断参数是否⻬全 # 判断⽤户名是否…

Sui Move:基本概览一

Module (模块) Move 代码被组织成模块, 可以把一个模块看成是区块链上的一个智能合约 可以通过调用这些模块中的函数来与模块进行交互&#xff0c;可以通过事务或其他 Move 代码来实现, 事务将被发送到并由Sui区块链进行处理&#xff0c;一旦执行完成&#xff0c;结果的更改将…

matlab的绘图的标题中(title)添加标量以及格式化输出

有时候我们需要在matlab绘制的图像的标题中添加一些变量&#xff0c;这样在修改某些参数后&#xff0c;标题会跟着一块儿变。可以采用如下的方法&#xff1a; x -10:0.1:10; %x轴的范围 mu 0; %均值 sigma 1; %标准差 y normpdf(x,mu,sigma); %使用normpdf函数生成高斯函数…

微服务的自我修养:从拆分到秩序的进化论

文章背景 还记得我第一次接触微服务的场景&#xff0c;那是一个炎热的夏天。系统上线的前一天&#xff0c;单体应用出了点小问题&#xff0c;结果整个平台瘫痪了&#xff01;所有人手忙脚乱修复&#xff0c;但复杂的代码逻辑让进度异常缓慢。 后来听说可以用微服务架构来拆分系…

YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练

数据标注 前期准备 先打开Anaconda Navigator&#xff0c;点击Environment&#xff0c;再点击new(new是我下载anaconda的文件夹名称)&#xff0c;然后点击创建 点击绿色按钮&#xff0c;并点击Open Terminal 输入labelimg便可打开它,labelimg是图像标注工具&#xff0c;在上篇…

【c语言】指针 (完结)

一、sizeof和strlen的对比 1、sizeof 前面我们在学习操作符的时候&#xff0c;我们学习了sizeof&#xff0c;知道其是计算变量所占内存的大小的&#xff0c;单 位是字节&#xff0c;如果操作数是数据类型的话&#xff0c;计算的就是这个类型的变量所占的内存空间的大…

成语知识竞赛主持稿及串词

一、开场白 A&#xff1a;尊敬的各位老师 B&#xff1a;亲爱的同学们 合&#xff1a;大家好&#xff01; A&#xff1a;冬日的暖阳带着青春的气息扑面而来&#xff0c;我们迎来了XXX中学精英成语知识大赛。 B&#xff1a;欢迎各位来到成语大赛的现场。 A&#xff1a;成语是中华…

【Rust】结构体定义域实例化

目录 思维导图 1. 结构体的定义与实例化 1.1 结构体的基本概念 1.2 定义结构体 1.3 创建结构体实例 1.4 结构体的定义与实例化示例 2. 访问与修改结构体字段 2.1 访问字段 2.2 修改字段 3. 结构体实例的构造函数 3.1 构造函数的定义 3.2 使用字段初始化简写 4. 结…

vue2修改表单只提交被修改的数据的字段传给后端接口

效果&#xff1a; 步骤一、 vue2修改表单提交的时候&#xff0c;只将修改的数据的字段传给后端接口&#xff0c;没有修改得数据不传参给接口。 在 data 对象中添加一个新的属性&#xff0c;用于存储初始表单数据的副本&#xff0c;与当前表单数据进行比较&#xff0c;找出哪些…

Docker 安装开源的IT资产管理系统Snipe-IT

一、安装 1、创建docker-compose.yaml version: 3services:snipeit:container_name: snipeitimage: snipe/snipe-it:v6.1.2restart: alwaysports:- "8000:80"volumes:- ./logs:/var/www/html/storage/logsdepends_on:- mysqlenv_file:- .env.dockernetworks:- snip…

Windows 11更新之后卡顿 (黑神话掉帧严重)问题探索

前提 Windows 11 晚上更新完 24h2 之后&#xff0c;第二天玩黑神话&#xff0c;才40多帧 之前开启插针&#xff0c;可以运行到 120 帧左右 我的配置 9600X 3080 版本退回 用系统自带的 goBack 版本退回 大概不到3分钟 帧数还是不对&#xff0c;于是重做了系统 重做系统 …

[云原生之旅] K8s-Portforward的另类用法, 立省两个端口

前言 此方法适用于Pod不需要大量连接的情况: 有多个pod在执行任务, 偶尔需要连接其中一个pod查看进度/日志;对pod执行一个脚本/命令; 不适用于大量连接建立的情况: pod启的数据库服务;pod启的Api服务;pod启的前端服务;pod启的Oss服务; Portforward简介 Portforward就是端…

宁德时代C++后端开发面试题及参考答案

请阐述面向对象的三大特性。 面向对象编程有三大特性&#xff0c;分别是封装、继承和多态。 封装是指将数据和操作数据的方法绑定在一起&#xff0c;对数据的访问和操作进行限制。这样做的好处是可以隐藏对象的内部细节&#xff0c;只暴露必要的接口给外部。例如&#xff0c;我…