一次 JVM 进程退出的原因分析

文章讨论了在Docker环境下,JVM进程在新版APM下因main线程退出而快速退出的现象,分析了System.in.read()在容器中的行为以及JVM退出条件的变化,重点关注了非守护线程的作用。作者通过源码分析和工具验证,揭示了新版APM中由于缺乏常驻非守护线程导致的JVM退出差异。
摘要由CSDN通过智能技术生成

在之前的文章《关于 /dev/null 差点直播吃鞋的一个小问题》中,我们分析过容器中的 stdin 指向 /dev/null。/dev/null 是一个特殊的设备文件,所有接收到的数据都会被丢弃。有人把 /dev/null 比喻为 “黑洞”,从 /dev/null 读数据会立即返回 EOF, System.in.read() 调用会直接退出。这篇文章的链接在这里:mp.weixin.qq.com/s/lYajWCb-o…

所以执行 main 函数以后,main 线程就退出了,新旧 APM 都一样。接下来就是要弄清楚一个常见的问题:一个 JVM 进程什么时候会退出。

JVM 进程什么时候会退出

================================================================================

关于这个问题,Java 语言规范《12.8. Program Exit》小节里有写,链接在这里:docs.oracle.com/javase/spec… ,我把内容贴在了下面。

A program terminates all its activity and exits when one of two things happens:

  • All the threads that are not daemon threads terminate.

  • Some thread invokes the exit method of class Runtime or class System, and the exit operation is not forbidden by the security manager.

翻译过来也就是导致 JVM 的退出只有下面这 2 种情况:

  • 所有的非 daemon 进程退出

  • 某个线程调用了 System.exit( ) 或 Runtime.exit() 显式退出进程

第二种情况当然不符合我们的情况,那嫌疑就放在了第一个上面,也就是换了新版本的 APM 以后,没有非守护进程在运行,所以 main 线程一退出,整个 JVM 进程就退出了。

接下来我们来验证这个想法,方法就是使用 jstack,为了不让接入了新版 APM 的 JVM 退出,先手动加上一个长 sleep。重新打包编译运行镜像,使用 jstack dump 出线程堆栈,可以直接阅读,或者使用「你假笨大神 PerfMa」公司的线程分析 XSheepdog 工具来分析。

image

可以看到,新版 APM 里,只有一个阻塞在 sleep 上的 main 线程是非守护线程,如果这个线程也退出了,那就是所有的非守护线程都退出了。这里的 main 没有退出还是后来加了 sleep 导致的。

接下来对比一下旧版 APM,XSheepdog 分析结果如下所示。

image

可以看到旧版 APM 里有 5 个非守护线程,其中 4 个非守护线程正是旧版 APM 内部的常驻线程。

到这里,原因就比较清楚了,在 docker 环境中 System.in.read() 调用不会阻塞,会立即退出,main 线程会结束。在旧版里,因为有常驻的非守护的 APM 处理线程在运行,所有整个 JVM 进程不会退出。在新版里,因为没有这些常驻的非守护线程,main 线程退出以后,就不存在非守护线程了,整个 JVM 就退出了。

源码分析

=======================================================================

接下的源码分析以下面这段 Java 代码为例,

public class MyMain {

public static void main(String[] args) {

System.out.println(“in main”);

}

}

复制代码

接下来我们来调试源码看看,JVM 运行以后会进入 java.c 的 JavaMain 方法,

int JNICALL JavaMain(void * _args) {

// …

/* Initialize the virtual machine */

InitializeJVM();

// 获取 public static void main(String[] args) 方法

mainID = (*env)->GetStaticMethodID(env, mainClass, “main”,

“([Ljava/lang/String;)V”);

// 调用 main 方法

(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

// main 方法结束以后接下来调用下面的代码

LEAVE();

}

复制代码

JavaMain 方法内部就是做了 JVM 的初始化,然后使用 JNI 调用了入口类的 public static void main(String[] args) 方法,如果 main 方法退出,则会调用后面的 LEAVE 方法。

#define LEAVE() \

do { \

if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \

JLI_ReportErrorMessage(JVM_ERROR2); \

ret = 1; \

} \

if (JNI_TRUE) { \

(*vm)->DestroyJavaVM(vm); \

return ret; \

} \

} while (JNI_FALSE)

复制代码

LEAVE 方法调用了 DestroyJavaVM(vm); 来触发 JVM 退出,这个退出当然是有条件的。destroy_vm 的源码如下所示。

image

可以看到,JVM 会一直等待 main 线程成为最后一个要退出的非守护线程,否则也没有退出的必要。这使用了一个 while 循环等待条件的发生。如果自己是最后一个,就可以准备整个 JVM 的退出了。

也可以把代码稍作修改,新建一个常驻的非守护线程 t,隔 3s 轮询 /tmp/test.txt 文件是否存在。main 线程在 JVM 启动后马上就退出了。

public class MyMain {

public static void main(String[] args) {

Thread t = new Thread(new Runnable() {

@Override

public void run() {

File file = new File(“/tmp/test.txt”);

while(true) {

if (file.exists()) {

break;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总目录展示

该笔记共八个节点(由浅入深),分为三大模块。

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。

高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。


篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)

由于内容太多,这里只截取部分的内容。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
烦各位转发一下(可以帮助更多的人看到哟!)

[外链图片转存中…(img-b0d3k2Ce-1713565283509)]

[外链图片转存中…(img-JmidV7b8-1713565283510)]

由于内容太多,这里只截取部分的内容。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值