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

张开发
2026/5/17 14:30:49 15 分钟阅读
揭秘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详细代码如下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);}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);resultlink.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 代码执行过程中发生了异常ExceptionJavaCallWrapper的析构过程会确保异常被正确捕获并传递回 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 的跨语言调用从“一次危险的跳转”变成了“一次受控的事务”它让 GC 能够识别栈通过 Anchoring。它让内存能够自动回收通过 HandleMark。它让异常能够安全传递通过 RAII 析构。

更多文章