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

揭秘JVM创世过程之JavaCallWrapper时空穿梭的检察官

前言

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

前情回顾

在前文揭秘JVM创世过程之Call Stub进入Java世界的门票中提到Call Stub是进入Java世界的"跳板",这种“运行时生成代码”的技术(JIT 思想的萌芽),确保了 Java 能够跨越 C++ 和字节码的鸿沟,同时保持顶级的执行效率。

Java与C++时空间穿梭的检察官JavaCallWrapper

通过阅读源码hotspot\src\share\vm\runtime\javaCalls.cpp,可以看出StubRoutines::call_stub()执行前有一行代码JavaCallWrapper link(method, receiver, result, CHECK)
hotspot\src\share\vm\runtime\javaCalls.cpp详细代码如下:

  1. JavaCalls::call()方法源码
voidJavaCalls::call(JavaValue*result,methodHandle method,JavaCallArguments*args,TRAPS){// 省略部分代码// Need to wrap each and everytime, since there might be native code down the// stack that has installed its own exception handlersos::os_exception_wrapper(call_helper,result,&method,args,THREAD);}
  1. JavaCalls::call_helper()方法源码
voidJavaCalls::call_helper(JavaValue*result,methodHandle*m,JavaCallArguments*args,TRAPS){// 省略部分代码// do call{JavaCallWrapperlink(method,receiver,result,CHECK);{HandleMarkhm(thread);// HandleMark used by HandleMarkCleanerStubRoutines::call_stub()((address)&link,// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)result_val_address,// see NOTE above (compiler problem)result_type,method(),entry_point,args->parameters(),args->size_of_parameters(),CHECK);result=link.result();// circumvent MS C++ 5.0 compiler bug (result is clobbered across call)// Preserve oop return value across possible gc pointsif(oop_result_flag){thread->set_vm_result((oop)result->get_jobject());}}}// Exit JavaCallWrapper (can block - potential return oop must be preserved)// 省略部分代码}

这段代码JavaCallWrapper link(method, receiver, result, CHECK);JavaCalls机制中的核心安全锁扣
如果把Call Stub比作穿梭两个世界的“传送门”,那么JavaCallWrapper就是那个记录你身份、帮你提行李、并在你回来时负责检查身体的检察官

hotspot\src\share\vm\runtime\javaCalls.hpp中这位检察官(JavaCallWrapper)的真实面目如下:

// A JavaCallWrapper is constructed before each JavaCall and destructed after the call.// Its purpose is to allocate/deallocate a new handle block and to save/restore the last// Java fp/sp. A pointer to the JavaCallWrapper is stored on the stack.classJavaCallWrapper:StackObj{friendclassVMStructs;private:JavaThread*_thread;// the thread to which this call belongsJNIHandleBlock*_handles;// the saved handle blockMethod*_callee_method;// to be able to collect arguments if entry frame is top frameoop _receiver;// the receiver of the call (if a non-static call)JavaFrameAnchor _anchor;// last thread anchor state that we must restoreJavaValue*_result;// result valuepublic:// Construction/destructionJavaCallWrapper(methodHandle callee_method,Handle receiver,JavaValue*result,TRAPS);~JavaCallWrapper();// AccessorsJavaThread*thread()const{return_thread;}JNIHandleBlock*handles()const{return_handles;}JavaFrameAnchor*anchor(void){return&_anchor;}JavaValue*result()const{return_result;}// GC supportMethod*callee_method(){return_callee_method;}oopreceiver(){return_receiver;}voidoops_do(OopClosure*f);boolis_first_frame()const{return_anchor.last_Java_sp()==NULL;}};

它利用了 C++ 的RAII(资源获取即初始化)机制:构造时建立连接,析构时撤销连接。以下是它的具体作用:


1. 建立“栈锚点”(Stack Anchoring)

这是它最重要的功能。JVM 的垃圾回收器(GC)需要经常扫描线程栈。

  • 痛点:当线程从 C++ 跳入 Java 后,栈变得“混乱”了——一部分是 C++ 栈帧,一部分是 Java 栈帧。
  • 作用JavaCallWrapper会记录当前的栈指针(SP)和帧指针(FP)。它在JavaThread对象中设置一个“Anchor”(锚点)
  • 意义:当 GC 发生时,它能通过这个锚点知道:“从这里开始,往上是 Java 世界,往下是 C++ 世界”。没有这个锚点,GC 就无法准确地遍历栈,会导致内存泄漏或崩溃。

2. 句柄保护(HandleMarking)

在执行 Java 代码期间,可能会产生大量的临时对象引用(Handles)。

  • 作用JavaCallWrapper内部通常会持有一个HandleMark
  • 意义:当 Java 方法执行完毕,link对象被销毁(析构)时,它会自动清理在这次调用期间产生的内部句柄。这保证了即使你反复从 C++ 调用 Java,也不会因为句柄堆积而导致内存溢出。

3. 线程状态的“值班记录”

在进入 Java 世界前,线程的状态必须被正确标记。

  • 作用:它协助管理线程状态从_thread_in_vm_thread_in_Java的转换。
  • 异常处理:如果在 Java 代码执行过程中发生了异常(Exception),JavaCallWrapper的析构过程会确保异常被正确捕获并传递回 C++ 层,而不是直接让整个进程死掉。

4. 源码逻辑拆解(构造与析构)

我们可以通过伪代码来看这个link对象的生命周期:

{// 1. 构造函数被调用 (Constructor)// - 记录当前的 JavaFrameAnchor// - 检查并保存当前的 HandleMark// - 在线程对象中登记这次调用的深度JavaCallWrapperlink(method,receiver,result,CHECK);// 2. 这里的代码真正跳入 Call Stub 执行 Javastub->call(...)// 3. 退出作用域,析构函数自动调用 (Destructor)// - 恢复之前的 JavaFrameAnchor(把锚点拔掉)// - 恢复之前的线程状态// - 清理本次调用产生的临时 Handles}

5. 为什么叫 “link”?

在 OpenJDK 的命名习惯里,它被称为link是因为它确实连接了两个世界的上下文。 在JavaThread类中,有一个指针指向当前的JavaCallWrapper。如果发生了嵌套调用(C++ -> Java -> C++ -> Java),这些link对象会在内存中形成一个链表。这让 JVM 在任何时候都能回溯出整个复杂的跨语言调用链。


总结

JavaCallWrapper link的存在,让 JVM 的跨语言调用从“一次危险的跳转”变成了“一次受控的事务”:

  1. 它让 GC 能够识别栈(通过 Anchoring)。
  2. 它让内存能够自动回收(通过 HandleMark)。
  3. 它让异常能够安全传递(通过 RAII 析构)。
http://www.jsqmd.com/news/583235/

相关文章:

  • AI大模型的简历如何写才能拿到面试机会?简历+项目+面试技巧+面试题一套全搞定!
  • Ex-Human起诉苹果,下架纠纷引关注
  • 5大核心功能完全掌握:ModTheSpire高效模组管理进阶指南
  • 5大核心功能打造高效媒体播放:免费开源解码工具LAV Filters全解析
  • 如何一次性解决Windows DLL缺失问题:VisualCppRedist AIO终极指南
  • 区块链智能合约测试安全指南:面向测试工程师的专业实践
  • 基于灰狼优化算法(GWO)的无人机协同路径规划
  • (含下载)WP Mail SMTP Pro WordPress插件使用教程
  • OpenClaw × 88API:10 分钟搭好本地网关,解决 API 超时和多渠道切换(2026 完整教程)
  • 双容水箱液位控制:从PLC梯形图到上位机组态
  • GIMP Resynthesizer终极指南:5分钟掌握专业级图像修复与纹理合成
  • 围绕 Email MCP Tool 的一次工程化尝试,以及 DМχΑРΙ 的出现
  • ollama在项目中,可以随意切换大模型吗。比如安装了qwen,llama,ds-r1等模型。
  • OpenMS深度解析:质谱数据分析的终极解决方案与架构创新
  • 2023B卷,磁盘容量
  • Linux/C++多进程
  • 如何高效搭建科技服务平台?
  • Go Goroutine 与用户态是进程级
  • 大以论文与万方、维普、WPS AI 综合对比(2026)
  • AutoCAD数据处理的.NET解决方案:ACadSharp全功能指南
  • 学习进度4/3
  • OpenHTMLtoPDF字体加载机制深度解析:从Jar包困境到优雅解决方案
  • Windows下安装redis
  • 20260403 紫题训练
  • 2026 知识付费 SaaS 选型:谁才是长期变现首选
  • 如何永久保存微信聊天记录:WeChatMsg本地化数据管理完全指南
  • 推荐人证访客一体机品牌厂家,配高端来访人员预约+外来访客登记门禁管理系统 - 智能硬件-产品评测
  • hmailserver+Mysql邮件服务器环境搭建
  • 2026湖州装修公司怎么选?口碑、报价、服务全方位深度推荐指南 - GrowthUME
  • 企业级AI智能体平台技术评测:9款产品架构差异与生产落地能力分析