前言

本文旨在记录近期研读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()方法源码

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);
}

  1. 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 的跨语言调用从“一次危险的跳转”变成了“一次受控的事务”:

  1. 它让 GC 能够识别栈(通过 Anchoring)。
  2. 它让内存能够自动回收(通过 HandleMark)。
  3. 它让异常能够安全传递(通过 RAII 析构)。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐