news 2026/4/14 18:27:09

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
揭秘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系统调用,销毁整个物理进程。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 18:25:23

终极指南:5个步骤让经典DirectX游戏在现代Windows系统重获新生

终极指南&#xff1a;5个步骤让经典DirectX游戏在现代Windows系统重获新生 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_mirrors/d…

作者头像 李华
网站建设 2026/4/14 18:25:14

代码上传阿里云代码库

1.代码写完之后进入代码文件夹cmd回车回车之后去阿里云代码库找对应的第二个方式cd existing_folder git init git remote add origin https://codeup.aliyun.com/66c456ff7bbf70c628590242/test.git git add . git commit git push -u origin HEAD可以先修改head为自己的主分支…

作者头像 李华
网站建设 2026/4/14 18:21:18

如何彻底告别网盘限速:8大主流网盘直链解析完整指南

如何彻底告别网盘限速&#xff1a;8大主流网盘直链解析完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云…

作者头像 李华