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

揭秘JVM创世过程之紧急制动机制-异常处理

前言

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

Java世界的紧急制动机制

在 OpenJDK 8u44 的源码中,当 Java 初始化期间(例如执行System.initializeSystemClass)抛出异常时,C++ 层感知并终止 VM 的过程并非通过 C++ 的try-catch机制,而是通过一套基于“挂起异常字段(Pending Exception)”“CHECK 宏”的显式检查机制。

以下是这一过程的深度拆解:

1. 第一现场:JavaThread的异常槽位

在 JVM 内部,每个线程(JavaThread)都有一个专门存放异常对象的指针字段。

  • _pending_exception:记录Java 层的异常对象(oop)

  • _exception_file:记录抛出异常的 C++ 文件

  • _exception_line:抛出异常的行号

  • 源码位置:下面是整理后的示例代码。

classThread:publicThreadShadow{// ...oop _pending_exception;// 指向 Java 层的异常对象(oop)constchar*_exception_file;// 抛出异常的 C++ 文件int_exception_line;// 抛出异常的行号};

hotspot\src\share\vm\runtime\thread.hpp

  • 原始代码
classThread:publicThreadShadow{friendclassVMStructs;private:// Exception handling// (Note: _pending_exception and friends are in ThreadShadow)//oop _pending_exception; // pending exception for current thread// const char* _exception_file; // file information for exception (debugging only)// int _exception_line; // line information for exception (debugging only)// 省略部分代码}

hotspot\src\share\vm\utilities\exceptions.hpp

classThreadShadow:publicCHeapObj<mtThread>{friendclassVMStructs;protected:oop _pending_exception;// Thread has gc actions.constchar*_exception_file;// file information for exception (debugging only)int_exception_line;// line information for exception (debugging only)friendvoidcheck_ThreadShadow();// checks _pending_exception offset// 省略部分代码}

当 Java 代码通过call_stub执行并抛出异常时,Java 异常处理机制(或者解释器/JIT 编译的代码)会将异常对象的引用存入这个_pending_exception字段,然后直接返回到 C++。

2. 感知机制:万能的CHECK

Threads::create_vm等初始化代码中,你会看到大量的 C++ 调用带有一个特殊的宏。这是 C++ 感知 Java 异常的“神经末梢”。

  • 源码位置src/share/vm/runtime/exceptions.hpp
#defineCHECKTHREAD);if(HAS_PENDING_EXCEPTION)return;(void)(0#defineCHECK_0THREAD);if(HAS_PENDING_EXCEPTION)return0;(void)(0#defineCHECK_NHTHREAD);if(HAS_PENDING_EXCEPTION)returnHandle();(void)(0

工作原理

每当 C++ 层调用一个可能触发 Java 代码的函数(如JavaCalls::call_static)时,紧接着就会执行if (HAS_PENDING_EXCEPTION)。这个宏会检查当前线程的_pending_exception是否为空。如果不为空,说明 Java 层出事了,C++ 函数会立即提前返回,将异常向上传递。

3. 拦截点:Threads::create_vm中的关键检查

在 VM 启动的核心函数Threads::create_vm中,JVM 会分阶段检查初始化进度。如果核心类加载或初始化失败,流程如下:

  • 源码位置src/share/vm/runtime/thread.cpp
jintThreads::create_vm(JavaVMInitArgs*args,bool*canTryAgain){// ... 执行某项 Java 初始化任务 ...call_initialize_system_class(CHECK_0);// 如果这里抛出异常,直接返回 0// 或者显式检查if(HAS_PENDING_EXCEPTION){// 发现异常,进入收尾逻辑vm_exit_during_initialization(Handle(THREAD,PENDING_EXCEPTION));}}

4. 物理栈的回溯:穿过 Call Stub

当异常发生且未在 Java 层被捕获时,逻辑会回到Call Stub

  • 现场还原:Call Stub 执行其“收尾工作”,根据之前保存的rbprsp还原 C++ 的寄存器环境。
  • 平滑着陆:它并不处理异常逻辑,它只是保证 CPU 能够安全地跳回到JavaCalls::call_helper里的下一条 C++ 指令。
  • 状态确认:回到 C++ 后,由于CHECK宏的存在,当前的 C++ 函数会意识到“出事了”,从而停止后续的初始化逻辑(如停止加载其他类)。

5. 终结者:vm_exit_during_initialization

一旦 C++ 层确认在初始化这种关键时刻发生了异常,它不会尝试恢复,而是直接调用vm_exit_during_initialization

  • 源码位置src/share/vm/runtime/java.cpp
voidvm_exit_during_initialization(Handle exception){// 1. 打印异常堆栈,让你看到那个著名的 "Error occurred during initialization of VM"java_lang_Throwable::print_stack_trace(exception,tty);// 2. 终止 VMvm_abort(false);}

这个函数会做两件事:

  • 动作
    1. 翻译异常:调用java_lang_Throwable::print_stack_trace。虽然此时 Java 环境还没完全好,但 JVM 已经有足够的“硬核”能力去解析那个异常对象的 backtrace,并打印到 stderr。否则直接在 stderr 打印 C++ 侧记录的异常名。
    2. 停掉所有线程:通知安全点(Safepoint)机制,尝试停止其他可能正在运行的辅助线程。
    3. 销毁内存:释放已经分配的部分本地资源。
    4. 退出进程:直接调用os::exit(1)abort(),不给 Java 留任何挣扎的机会,强制关闭整个 JVM 进程。

6. 为什么不直接用 C++ 的try-catch

这是一个经典的架构选择真相:

  1. 性能与受控:C++ 异常在跨模块(尤其是在汇编生成的call_stub和 C++ 之间)传递时开销极大且难以预测。
  2. 隔离性:Java 的异常是 Java 堆里的对象(oop),而 C++ 的异常是系统级的。JVM 需要精确控制“谁抛出了异常”以及“这个异常对象在 GC 扫描时是否存活”,通过JavaThread字段来手动维护这种关系,比依赖 C++ 运行时要稳健得多。

总结

在Java世界感知异常的真相是:

架构选择真相:主动轮询
  1. 性能与受控:C++ 异常在跨模块(尤其是在汇编生成的call_stub和 C++ 之间)传递时开销极大且难以预测。
  2. 隔离性:Java 的异常是 Java 堆里的对象(oop),而 C++ 的异常是系统级的。JVM 需要精确控制“谁抛出了异常”以及“这个异常对象在 GC 扫描时是否存活”,通过JavaThread字段来手动维护这种关系,比依赖 C++ 运行时要稳健得多。
  3. Java 执行逻辑负责**“写”**异常标志(_pending_exception)。
  4. C++ 调用者负责在每一行关键代码后通过CHECK宏**“读”**异常标志。
提前布局:在进入 main 方法时的物理栈布局与异常感知

当你进入main方法时,物理栈就像一个精心布置的陷阱阵列:

  1. Java 栈帧(最顶端):一旦抛出异常,这里只负责填充异常信息。
  2. Call Stub 汇编层:作为“时空隧道”,它负责把 CPU 控制权从 Java 语义安全地交还给 C++ 语义。
  3. C++ 逻辑层(底层):通过CHECK像接力赛一样监控JavaThread->_pending_exception字段。
从 Java 到 C++ 的感应链条
  1. Java 层:发生异常,将异常对象塞进JavaThread_pending_exception
  2. 汇编层 (call_stub):清理 Java 栈帧,跳回 C++ 调用处。
  3. C++ 层 (JavaCalls):函数返回,通过HAS_PENDING_EXCEPTION宏感知到异常。
  4. VM 核心 (create_vm):发现异常在初始化关键路径上,调用vm_exit_during_initialization
  5. OS 层:C++ 发起exit系统调用,销毁整个物理进程。
http://www.jsqmd.com/news/640751/

相关文章:

  • Windows风扇终极控制指南:3分钟掌握FanControl免费软件
  • 智能财务是什么?怎么实操智能财务?
  • Thinkpad T470p杜比音效丢失?三步找回并增强(附FxSound搭配技巧)
  • 浏览器中的专业演示文稿编辑器:PPTist如何重塑在线演示体验
  • DevOps工具链选型新趋势:本土化适配与安全可控成企业核心考量
  • 从深夜告警到真相大白:手把手复盘一次Windows服务器被黑应急响应全过程
  • 用STM32CubeMX和TensorFlow Lite,手把手教你部署一个10KB的AI分类器到F407
  • 终极抢票神器:DamaiHelper让你的演唱会门票不再错过
  • LocalVocal:完全免费的本地AI语音识别与实时字幕解决方案
  • 经典 PLC 程序(1) - 起保停
  • 如何彻底告别网盘限速:8大主流网盘直链解析完整指南
  • 【前端进阶】深入浅出Vue渲染函数:从基础到动态组件实战
  • Navicat连接MySQL8.0失败
  • 济南包车带司机多少钱?2026最新行情+全场景报价,携程百事通手把手教你避坑 - 土星买买买
  • GME-Qwen2-VL-2B-Instruct部署与Node.js环境配置:打造全栈AI应用后端
  • Wan2.1-umt5处理长文本实战:基于LSTM的上下文优化效果展示
  • Bunker_mini_dev实战:基于Docker网络隔离,在Jetson Orin NX上并行驱动AVIA与MID-360激光雷达
  • 2026 国内代理 IP 实测:快代理独享 IP 和共享 IP 到底怎么选更稳
  • PX4多机集群控制:5大技术挑战与分布式解决方案深度解析
  • 用Cesium + Shadertoy打造动态天气:一个雷电球体材质的完整实现与参数调优
  • 代码实现
  • 数据结构面试必问:6大排序算法实战对比(附Python代码)
  • Performance 面板结构总览逐区域解释
  • 从一根铜缆到40公里光纤:手把手教你部署QSFP模块的5种典型连接方案
  • Windows 10/11下达梦数据库8.0安装避坑指南(附常见错误解决方案)
  • UE5第三人称Camera实战:从基础搭建到平滑移动与旋转控制
  • 信道相关性对MIMO性能的影响:实测数据告诉你天线间距该怎么设置
  • IDaaS选型指南:拒绝盲目跟风,教你选出最适合企业的“超级门神”
  • 关于vs1003播放midi播放不完整问题
  • 全文降AI率怎么操作最高效?3款工具分步教程对比