当前位置: 首页 > news >正文

专家视角看Java线程线程退出时的资源拆解工程

Java线程线程退出时的资源拆解工程

    • 前言
    • 线程退出时如何清理 JNI Handles、注销安全点(Safepoint)并回收栈内存
      • 1. 清理 JNI Handles:解除与 Native 世界的绑定
      • 2. 注销安全点(Safepoint):从 VM 活跃名单中抹除
      • 3. 回收栈内存:释放物理资源
        • A. 解除警戒页(Guard Pages)
        • B. 释放 C++ 管理对象
        • C. 物理栈的回笼(The munmap Moment)
      • 总结:线程退出的全路径清单
      • 深度思考

前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。

线程退出时如何清理 JNI Handles、注销安全点(Safepoint)并回收栈内存

OpenJDK 8的底层实现中,线程的退出(Exit)是一场比启动更为复杂的“资源拆解”工程。当 Java 层的run()方法执行结束,执行流回到 C++ 世界时,JVM 必须确保该线程占用的所有非堆内存(Native Memory)、元数据关联以及内核资源被彻底回收,否则会引发严重的内存泄漏或导致系统在下一次安全点(Safepoint)同步时挂起。线程的退出过程被称为**“葬礼协议(Funeral Protocol)”**。这不仅是 Java 对象的销毁,更是 Native 资源的彻底交接。整个过程由hotspot/src/share/vm/runtime/thread.cpp中的JavaThread::exit函数总控。

以下从底层源码视角分析 JNI 句柄清理、安全点注销及栈内存回收的完整路径。


1. 清理 JNI Handles:解除与 Native 世界的绑定

每个 Java 线程在执行 JNI 调用时,都会产生局部引用(Local References),这些引用存储在线程私有的JNIHandleBlock中。

  • 源码定位与逻辑:在hotspot/src/share/vm/runtime/thread.cppJavaThread::exit方法中,可以看到对 Handle 的清理:
// 源码示意if(active_handles()!=NULL){JNIHandleBlock*block=active_handles();set_active_handles(NULL);JNIHandleBlock::release_block(block);}
  • 源码逻辑:在JavaThread::exit内部,JVM 会调用active_handles()->zap()或直接释放块。

    • 句柄块(Handle Block)结构:JNI 局部引用并不直接存在于 Java 堆,而是存在于 Native 堆的一系列 Block 中。如果线程退出时不释放,这些指向 Java 对象的“根”就会永远存在,导致 Java 对象无法被 GC 回收。
    • release_block的动作:它会将当前线程占用的JNIHandleBlock归还到全局的空闲列表(Free List)中,以便后续新创建的线程复用,避免频繁的malloc开销。
      • 物理动作
        1. 遍历当前线程持有的所有JNIHandleBlock
        2. 将块内的所有句柄位置清零(Zap)。
        3. 将这些 Block 归还给全局的空闲块列表(Free List),供其他线程复用。
      • 意义:这确保了线程死亡后,其曾经持有的 JNI 引用不会阻碍下一次垃圾回收(GC)对相关对象的扫描。
  • 清理机制

    • JVM 维护一个_active_handles指针。在线程退出时,它会遍历并释放这些JNIHandleBlock
    • 重要细节:如果线程是非正常退出(如由于致命错误),JVM 会通过JNIHandleBlock::release_block确保这些分配在 Native 堆上的句柄块被归还给全局空闲列表(Free List),防止内存泄漏。
  • 深度解析:这也是为什么即便你在 JNI 中忘记调用DeleteLocalRef,线程退出时也会作为最后一道防线进行强制清理。


2. 注销安全点(Safepoint):从 VM 活跃名单中抹除

这是确保 JVM 性能最关键的一步。一个已经“死亡”的线程绝不能出现在下一次 GC 的等待名单中。也是线程退出过程中最危险的阶段。如果一个线程已经停止运行,但仍留在 JVM 的全局线程列表(Threads List)中,当 VM Thread 发起安全点请求时,它会陷入永无止境的等待,导致著名的Safepoint Timeout

  • 核心函数Threads::remove(JavaThread* p)。在JavaThread::exit的中后期,线程会申请Threads_lock锁并调用注销逻辑:

    // 源码位置:hotspot/src/share/vm/runtime/thread.cppThreads::remove(this);
  • 操作流程

    1. 竞争全局锁:进入Threads::remove必须持有Threads_lock锁。
    2. 移出全局列表:JVM 维护着一个ThreadsListremove操作会将当前JavaThread对象从全局的ThreadsList链表中移除。
    3. 减少计数:递减_number_of_threads计数器。
  • 对 Safepoint 的影响

    • SafepointSynchronize::begin()中,VM 线程会遍历ThreadsList来下达暂停指令。
    • 注销意义:一旦Threads::remove完成,该线程对 VM 来说就是“不可见”的,这意味着该线程已经完成了它的“安全使命”,不再需要响应任何同步请求。GC 发生时,VM 将不再等待该线程报告其状态,避免了因为僵尸线程导致的“长停顿(Long STW)”。

3. 回收栈内存:释放物理资源

线程栈(Thread Stack)是在 start 时通过 mmap 或 pthread_create 申请的 Native 内存,通常为 1MB。栈内存的回收分为两个维度:JVM 层面的JVM 逻辑保护位的解除和OS 层面的系统物理栈的释放

A. 解除警戒页(Guard Pages)
  • 源码逻辑:调用os::remove_stack_guard_pages()
  • 原理:在 Linux 上,JVM 通过mprotect将栈底设为不可访问以探测溢出。退出时,必须通过系统调用撤销这些内存页的特殊权限,将其归还为普通的、可分配的内存状态。它通过mprotect(addr, size, PROT_READ|PROT_WRITE)将 Yellow/Red Zone 恢复为可读写状态。
B. 释放 C++ 管理对象
  • JavaThread 析构:执行delete thread。这会触发JavaThread及其内部OSThread的析构函数,释放分配在 C++ 堆上的元数据。
C. 物理栈的回笼(The munmap Moment)
  • 关键点:由于 HotSpot 在创建线程时使用了PTHREAD_CREATE_DETACHED标志(在os_linux.cppos::create_thread中设置)。
  • 内核行为
    1. 线程执行完java_start并返回,最终触发pthread_exit。这意味着当pthread_exit被调用且线程函数返回时,内核会自动回收该线程的所有资源,包括它的物理栈空间。
    2. OS 自动回收:由于是DETACHED状态,Linux 内核收到退出信号后,会自动执行munmap系统调用,释放该线程当初申请的那 1MB(默认值)虚拟内存。
    3. RSS 归还:此时,该栈对应的物理内存页(RSS)被标记为空闲,交还给操作系统内核调度。

总结:线程退出的全路径清单

步骤执行主体关键方法 (OpenJDK 8)物理/逻辑意义
1. 状态宣告JavaThreadset_thread_state(_thread_exiting)告诉 VM:我要走了,别在安全点等我。
2. Java 收尾JavaThreadensure_join(this)唤醒所有在该线程对象上join()的守候者。
3. JNI 清理JavaThreadJNIHandleBlock::release_block释放所有局部引用,断开与 Java 堆的隐形连接。
4. 注销身份VM (Global)Threads::remove(this)移出全局列表,彻底脱离 Safepoint 管控。
5. 内存去保护OS 抽象层os::remove_stack_guard_pages恢复栈底警戒页的读写权限。
6. 物理自毁OS 内核pthread_exit/munmap操作系统回收 1MB 栈空间及内核 Task 数据结构。

深度思考

我们需要关注到一个细节:JavaThread对象的销毁时间点

thread.cpp中,你会发现delete thread是在线程的“最后时刻”发生的。如果一个线程在执行JavaThread::exit时被外部通过SIGKILL强行终止,那么Threads::remove可能还没执行完,这会导致 JVM 在下一次进入 Safepoint 时产生极短的混乱甚至崩溃(Crash)。

因此,HotSpot 严禁在生产环境下使用kill -9针对特定 Java 线程(LWP),因为这会破坏上述精密有序的“葬礼协议”。

http://www.jsqmd.com/news/680168/

相关文章:

  • 给DSP新手:用TMS320F28335的PIE中断,从“肚子痛”到“手被割伤”都管起来
  • 2026年进口真空泵维修保养哪家好?进口真空泵维修保养公司推荐:天之华领衔,真空泵维修保养哪家靠谱精选榜单 - 栗子测评
  • 宜选影票API从工具变生态你知道吗 这波趋势真的能挖到大流量!
  • 直流稳压/电源定制厂家哪家好?2026直流稳压/通信电源/直流转换器优质厂家优选 - 栗子测评
  • 告别纯理论!用Wireshark抓包带你透视华为防火墙NAT64的转换全过程
  • 2026年钢格板厂家推荐合集:热镀锌钢格板厂家盘点,不锈钢/压焊/插接/平台钢格板厂家甄选 - 栗子测评
  • Flutter 鸿蒙应用离线模式实战:无网络也能流畅使用
  • 分子泵维修保养哪家好?进口分子泵维修保养哪家好?2026年精选进口分子泵维修保养公司推荐/分子泵维修公司推荐:天之华领衔 - 栗子测评
  • 5个智能功能让英雄联盟游戏体验提升300%:League Akari 终极解决方案
  • 2026北京灭火器干粉回收厂家名录:北京七氟丙烷回收/北京七氟丙烷检测/北京七氟丙烷灭火器回收/北京七氟丙烷灭火器检测/选择指南 - 优质品牌商家
  • 2026蜘蛛手上料站生产厂家全梳理:蜘蛛手摆盘机生产厂家合集 - 栗子测评
  • 计算机毕业设计:Python农作物生产数据智能分析系统 Django框架 数据分析 可视化 机器学习 深度学习 大数据 大模型(建议收藏)✅
  • 探秘三亚汽车租赁:服务周到的门店不容错过,评价好的汽车租赁联系方式优质品牌选购指南 - 品牌推荐师
  • 【26年最新四级】英语四级高频核心词汇1500个pdf电子版(考前必背单词)+真题
  • 2026Q2西南卧式生物质蒸汽发生器标杆名录及选购指南:四川卧式蒸汽锅炉、四川热水锅炉、四川燃气热水锅炉、四川燃气蒸汽发生器选择指南 - 优质品牌商家
  • AI概念“脱水”指南:从LLM到A2A,看懂大模型技术演进脉络!
  • 2026年PCB克隆怎么选:HDI抄板、IC解密、PCB克隆、PCB国产化、PCB抄板、PCB设计、STM32解密选择指南 - 优质品牌商家
  • 2026底轴旋转坝技术全解析:液压坝/溢流闸/翻板坝/船闸/节制闸/蓄水坝/进水闸/钢坝/钢闸门/防洪闸/合页坝/选择指南 - 优质品牌商家
  • 合同管理系统哪个好?2026 年选型指南
  • 从仿真到综合:手把手拆解Verilog中always@(*)与assign的真实差异(附Testbench调试技巧)
  • 从Word2Vec到BERT:一文读懂主流Embedding模型选型指南
  • 低温泵维修保养哪家好?2026年进口低温泵维修保养公司推荐:天之华领衔,全国优质服务商实力汇总 - 栗子测评
  • 别再傻傻全量引入antd了!React项目用craco+less-loader搞定按需加载与主题定制(附最新版本避坑指南)
  • (90页PPT)华为SDBE领先模型闭环战略管理的全面解析(附下载方式)
  • 【高炉炼铁领域炉温监测、预警、调控智能体设计与应用】~系列文章04:AI如何赋能高炉炼铁?
  • 2026柔性振动盘厂家推荐盘点:苏州振动盘厂家优质厂商 - 栗子测评
  • mysql如何使用yum安装mysql_配置官方yum源与自动安装
  • 2026年国内钢格栅板厂家合集:热镀锌钢格栅生产厂家盘点,沟盖板/踏步板/光伏走道板 - 栗子测评
  • TinyMCE 6.x 在Vue 3 + Vite项目中的完整配置与避坑指南(2024最新)
  • 选型必看2026柔性视觉上料机厂家推荐!柔性上料站定制厂汇总 - 栗子测评