揭秘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()方法源码
void JavaCalls::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 handlers
os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}
JavaCalls::call_helper()方法源码
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
// 省略部分代码
// do call
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread); // HandleMark used by HandleMarkCleaner
StubRoutines::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 points
if (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.
class JavaCallWrapper: StackObj {
friend class VMStructs;
private:
JavaThread* _thread; // the thread to which this call belongs
JNIHandleBlock* _handles; // the saved handle block
Method* _callee_method; // to be able to collect arguments if entry frame is top frame
oop _receiver; // the receiver of the call (if a non-static call)
JavaFrameAnchor _anchor; // last thread anchor state that we must restore
JavaValue* _result; // result value
public:
// Construction/destruction
JavaCallWrapper(methodHandle callee_method, Handle receiver, JavaValue* result, TRAPS);
~JavaCallWrapper();
// Accessors
JavaThread* thread() const { return _thread; }
JNIHandleBlock* handles() const { return _handles; }
JavaFrameAnchor* anchor(void) { return &_anchor; }
JavaValue* result() const { return _result; }
// GC support
Method* callee_method() { return _callee_method; }
oop receiver() { return _receiver; }
void oops_do(OopClosure* f);
bool is_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
// - 在线程对象中登记这次调用的深度
JavaCallWrapper link(method, receiver, result, CHECK);
// 2. 这里的代码真正跳入 Call Stub 执行 Java
stub->call(...)
// 3. 退出作用域,析构函数自动调用 (Destructor)
// - 恢复之前的 JavaFrameAnchor(把锚点拔掉)
// - 恢复之前的线程状态
// - 清理本次调用产生的临时 Handles
}
5. 为什么叫 “link”?
在 OpenJDK 的命名习惯里,它被称为 link 是因为它确实连接了两个世界的上下文。 在 JavaThread 类中,有一个指针指向当前的 JavaCallWrapper。如果发生了嵌套调用(C++ -> Java -> C++ -> Java),这些 link 对象会在内存中形成一个链表。这让 JVM 在任何时候都能回溯出整个复杂的跨语言调用链。
总结
JavaCallWrapper link 的存在,让 JVM 的跨语言调用从“一次危险的跳转”变成了“一次受控的事务”:
- 它让 GC 能够识别栈(通过 Anchoring)。
- 它让内存能够自动回收(通过 HandleMark)。
- 它让异常能够安全传递(通过 RAII 析构)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)