记一次库版本升级引起程序自动停止

记一次库版本升级引起程序自动停止

最近我们的应用升级了jedis 版本,版本从 2.10.2 升级 到3.8.0。发现我们的任务应用启动后立马自动关闭了。

这就奇怪了,为什么升级个版本,会导致程序启动后自动关闭呢。带着这个疑问我们看下代码。

表现如下,
在这里插入图片描述
程序main线程运行完,直接结束了。

0.前置知识,java程序什么时候会结束?

Java程序通常在以下几种情况下结束:

  1. 主线程执行完成:如果主线程运行完毕,且没有其他非守护线程运行,程序就会结束。主线程指的是程序的 main() 方法所在的线程。

  2. 调用 System.exit(int status) 方法:调用 System.exit() 方法可以强制结束程序,status 参数为0表示正常退出,非0表示异常退出。

  3. 所有非守护线程结束:当所有非守护线程(非 Daemon 线程)结束时,程序也会自动退出。守护线程不会阻止程序结束,当没有非守护线程时,守护线程自动停止。

  4. 出现未捕获的异常:如果在某个线程中出现未捕获的异常,且这个异常终止了主线程或所有非守护线程,程序也会结束。

  5. 外部操作强制终止:程序也可能因外部操作(如操作系统关闭、手动杀死进程等)而被终止。

通过这些方式,Java程序可以结束或退出。

简单分析一下得出,我们的程序在运行打印出CustomerServer start… 就是自动退出了,没有其他报错 ,应该就是第一种情况。主线程执行完成,

要满足主线程执行完成 结束应用我们需要条件是:

1.主线程运行完毕

2.没有其他非守护线程运行

1.守护线程与用户线程

java中分为两种线性 守护线程(daemon)与用户(user)线程。

守护线程(Daemon Thread)是Java中一种特殊的线程,主要用于执行一些辅助任务,如垃圾回收、缓存管理等。与普通线程(非守护线程)相比,守护线程的特点是它会在所有非守护线程结束后自动关闭。这意味着当应用程序中没有非守护线程在运行时,JVM会自动关闭所有守护线程。

用户线程 默认情况下我们创建的线程或线程池都是用户线程。

下面有个简单例子,我们简单测试下。

/*** songxulin*/
public class Test {public static void main(String[] args) throws Exception {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 10; i++) {// 打印 i 信息System.out.println("i:" + i + ",isDaemon:" +Thread.currentThread().isDaemon());try {// 休眠 100 毫秒Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}});// 设置为守护线程thread.setDaemon(true);// 启动线程thread.start();System.out.println("hello");}
}

运行结果

hello
i:1,isDaemon:true

可以发现当我们主线程执行完成后,thread没有继续输出,程序就结束。

将thread.setDaemon(true); 注释再运行

hello
i:1,isDaemon:false
i:2,isDaemon:false
i:3,isDaemon:false
i:4,isDaemon:false
i:5,isDaemon:false
i:6,isDaemon:false
i:7,isDaemon:false
i:8,isDaemon:false
i:9,isDaemon:false
i:10,isDaemon:false

我们主线程执行完成后,thread继续输出,程序才结束。

2.代码分析

有了上面的结论后我们分析下我们自己的代码。
我们这个项目是一个任务项目所以我们没有使用springboot而是一个传统的spring项目。
主要使用到的库有 mybatis、spring、xxl,jedis。

public class CustomerTaskApplication {private static final Logger LOGGER = LogManager.getLogger(CustomerTaskApplication.class);public static void main(String[] args) throws Exception {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");LOGGER.info("CustomerServer start...");}}

上面这个是入口类,就是简单初始化了spring容器。我们知道spring容器会将所有的bean对象进行初始化。
初始化时不同库会进行一些初始化逻辑,初始化逻辑里面也会启动一些线程池。如 数据库连接池,redis连接池。
初始化完成后我们的main方法接结束了。这时主线程已经运行完成结束了。

我们想要保证我们的程序不会自动停止肯定需要有 非 daemon线程运行。也就是再main方法执行完 之前我们需要启动至少一个用户线程,保证程序不会停止。

3.线程分析

想要知道为什么使用jedis 2.10.2程序不会退出,升级到3.8.0后程序会退出。我们得分析下线程运行情况,如果分析呢?我们可以使用jdk自带的jvisualvm进行分析。

jedis 2.10.2版本

我们先启动下jedis 2.10.2 版本下的应用,打开jvisualvm分析Theads的运行情况。
在这里插入图片描述

可以看到,在主线程结束前有这些线程在运行,也就是说在这些线程里面必须至少有一个是非守护线程。我们点击 thread dump,查看具体的栈信息。

我们发现 commons-pool-evictor-thread这个线程使用 非守护线程,而且一直处于运行状态。
在这里插入图片描述

因此 在Main主线程结束后,程序不会立刻停止。

我持续运行程序,发现后面 nioEventLoopGroup-2-1 这个线程也是一个常驻的用户线程。这个线程会在main线程结束后运行。

jedis 3.8.0版本

接下我们测试下 jedis 3.8.0升级后的版本
在这里插入图片描述

启动程序后我们发现,程序在主线程结束后程序立马结束了。我们发现 live threads 有13个 ,deamon threads有12个,由于main线程一定为用户线程因此因此其他的线程均为守护线程。

到此我们基本可以总结出程序停止的原因了

分析结果

在此我们可以得出结论,我们jedis 包升级后 commons-pool-evictor-thread 从用户线程变为 守护线程,因此应用在主线程结束后,由于没有发现其他存活的用户线程(或者还没来得及启动),因此程序会立即结束。

关于commons-pool-evictor-thread 改为守护线程可以查看 https://github.com/redis/jedis/issues/1956。

4.代码调整

我们想要程序不立马结束,我们得在在主线程结束后,至少要保证一个用户线程存活。从前面得分析我们得出 在程序运行一段时间后 nioEventLoopGroup-2-1会启动。这个线程是xxl-job, executor ExecutorRegistryThread启动的,而这个线程又是在spring 容器初始化bean对象时启动的。

由于我们main程序初始化spring容器后立马结束,因此xxl-job, executor ExecutorRegistryThread还没来得及启动 nioEventLoopGroup-2-1线程。所以由于没有用户线程在,导致程序立马结束了。

因此我们只需要让main线程等待一会让nioEventLoopGroup-2-1线程运行起来,这样由于有用户线程存在,程序不会停止。

下面我们调整一下代码,让主线程等待一会。

/*** songxulin*/
public class CustomerTaskApplication {private static final Logger LOGGER = LogManager.getLogger(CustomerTaskApplication.class);public static void main(String[] args) throws Exception {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");LOGGER.info("CustomerServer start...");//进程等待一会再退出Thread.sleep(60000);}}

在这里插入图片描述

再主线程等待一会后,nioEventLoopGroup-2-1非守护线程启动,这时程序不会结束,而是继续运行下去。

5.总结

遇到程序结束我们需要对jvm程序结束需要有了解,对应这个情况,本质上是需要我们对 deamon线程和用户线程有一定得理解。然后需要我们结合jvm工具分析程序的运行情况。

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

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

相关文章

C语言_指针_进阶

引言&#xff1a;在前面的c语言_指针初阶上&#xff0c;我们了解了简单的指针类型以及使用&#xff0c;下面我们将进入更深层次的指针学习&#xff0c;对指针的理解会有一个极大的提升。从此以后&#xff0c;指针将不再是难点&#xff0c;而是学习底层语言的一把利器。 本章重点…

vr体验馆计时收银软件试用版下载 佳易王VR游戏厅计时计费管理系统使用操作教程

一、前言 【软件试用版资源文件下载可以点击文章最后卡片了解】 vr体验馆计时收银软件试用版下载 佳易王VR游戏厅计时计费管理系统使用操作教程 VR体验馆计时计费软件是专门为VR体验馆设计的管理工具&#xff0c;旨在提高服务效率和客户的满意度。软件能够记录客户使用设备的…

vue组件调用生命周期

《vue基础学习-组件》提到组件传递数据方式&#xff1a; 1. props/$emit 父传子&#xff1a;子组件通过 props 显式声明 自定义 属性&#xff0c;接收父组件的传值。子传父&#xff1a;子组件通过 $emit() 显式声明 自定义 事件&#xff0c;父组件调用自定义事件接收子组件返…

Docker-compose提示specified IP address..configured subnets问题以及Docker容器相关操作记录保存

一、Docker-compose提示user specified IP address is supported only when connecting to networks with user configured subnets 在网上下载的一些docker-compose.yml在执行的时碰到过多次如下报错&#xff1a; ERROR: for 5307e2acb....user specified IP address is supp…

2024.10.17 软考学习笔记

刷题网站&#xff1a; 软考中级软件设计师在线试题、软考解析及答案-51CTO题库-软考在线做题备考工具

vue2项目 实现上边两个下拉框,下边一个输入框 输入框内显示的值为[“第一个下拉框选中值“ -- “第二个下拉框选中的值“]

效果: 思路: 采用vue中 [computed:] 派生属性的方式实现联动效果,上边两个切换时,下边的跟随变动 demo代码: <template><div><!-- 第一个下拉框 --><select v-model"firstValue"><option v-for"option in options" :key&q…

Github优质项目推荐 - 第六期

文章目录 Github优质项目推荐 - 第六期一、【WiFiAnalyzer】&#xff0c;3.4k stars - WiFi 网络分析工具二、【penpot】&#xff0c;33k stars - UI 设计与原型制作平台三、【Inpaint-Anything】&#xff0c;6.4k stars - 修复图像、视频和3D 场景中的任何内容四、【Malware-P…

适用于 Windows 的 4 个最佳免费数据恢复软件

计算机最重要的是用户数据。除了您的数据之外&#xff0c;关于计算机的其他一切都是可替换的。这三个是数据丢失的最常见原因&#xff1a; 文件/文件夹删除 丢失分区 损坏的分区 文件/文件夹删除是最常见的数据丢失类型。大多数时候&#xff0c;由于不小心删除文件/文件夹而…

限流是什么?如何限流?怎么限流?

概述 什么是限流 对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机 为什么要限流 因为互联网系统通常都要面对大并发大流量的请求,在突发情况下(最常见的场景就是秒杀、抢购),瞬时大流量会直接将系统打垮,无法…

html和css实现页面

任务4 html文件 任务5 htm文件 css文件 任务6 html文件 css文件 任务7 html文件 css文件

Java【多线程】synchronized关键字

目录 synchronized的特性 1.互斥 2.可重入 如何自己实现一个可重入锁&#xff1f; 关于死锁 死锁的第三种情况 N个线程M把锁 构成死锁的四个必要条件 java标准库中的线程安全类 线程不安全 线程安全 synchronized关键字-监视器锁monitor locker synchronized的特性 …

Tailscale自建中转服务器derper搭建笔记(基于docker)

自己搭建derper服务器&#xff0c;让Tailscale中转更流畅。 Tailscale是很好的远程组网工具&#xff0c;在两台机器P2P打洞成功的情况下可以实现网络直连&#xff0c;但如果打洞失败就会进行数据中转&#xff0c;我们的数据要跑到国外再跑回来&#xff0c;这样速度就很慢了。 …

STGCN解读(论文+代码)

一、引言 引言部分不是论文的重点&#xff0c;主要讲述了交通预测的重要性以及一些传统方法的不足之处。进而推出了自己的模型——STGCN。 二、交通预测与图卷积 第二部分讲述了交通预测中路图和图卷积的概念。 首先理解道路图&#xff0c;交通预测被定义为典型的时间序列预测…

Axure重要元件一——动态面板

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;动态面板 课程内容&#xff1a;认识动态面板、动态面板基本操作 应用场景&#xff1a;特定窗口、重要交互、长页面、容器等 一、认识动态面板 动态…

DeBiFormer:带有可变形代理双层路由注意力的视觉Transformer

https://arxiv.org/pdf/2410.08582v1 摘要 带有各种注意力模块的视觉Transformer在视觉任务上已表现出卓越的性能。虽然使用稀疏自适应注意力&#xff08;如在DAT中&#xff09;在图像分类任务中取得了显著成果&#xff0c;但在对语义分割任务进行微调时&#xff0c;由可变形…

软件测试面试题600多条及答案

这些问题都是软件测试领域常见的面试问题&#xff0c;以下是一些可能的答案&#xff1a; 什么是软件测试&#xff1f; 软件测试是一系列活动&#xff0c;旨在评估软件产品的质量和性能&#xff0c;以确保它符合规定的需求和标准。它包括执行程序或系统以验证其满足规定需求的过…

“探索Adobe Photoshop 2024:订阅方案、成本效益分析及在线替代品“

设计师们对Adobe Photoshop这款业界领先的图像编辑软件肯定不会陌生。如果你正考虑加入Photoshop的用户行列&#xff0c;可能会对其价格感到好奇。Photoshop的价值在于其强大的功能&#xff0c;而它的价格也反映了这一点。下面&#xff0c;我们就来详细了解一下Adobe Photoshop…

数据结构(8.2_1)——插入排序

插入排序 算法思想&#xff1a;每次将一个待排序的记录按其关键字大小插入到前面已排序好的子序列中&#xff0c;直到全部记录插入完成。 代码实现 #include <stdio.h>void InsertSort(int A[], int n) {int i, j.temp;for (i 1; i < n; i) {//将各元素插入已排好…

Axure重要元件二——内联框架

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;内联框架 课程内容&#xff1a;认识内联框架、基本嵌入 应用场景&#xff1a;表单、图片、文字嵌入式场景、交互应用 一、认识内联框架 内联框架的…

如何安全擦除 iPhone 上的所有数据,避免隐私泄露?

在当今的数字时代&#xff0c;隐私安全尤为重要。特别是在转让或出售 iPhone 之前&#xff0c;擦除设备上的所有内容是每位用户都应注意的关键步骤。尽管苹果自带了删除数据的功能&#xff0c;但有时这并不足以保证数据完全无法恢复。本文将结合 iPhone 自带的"抹掉所有内…