前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容可能存在疏漏,恳请读者不吝指正。
前情回顾
在揭秘JVM创世过程之世界法则切换-从C++法则到Java法则中,提到主线程诞生后,JVM从“C++ 法则”切换到“Java法则”,也就是执行第一个字节码,由一个核心工具类JavaCalls完成。下面就来介绍一下JavaCalls机制。
JavaCalls 机制
在java世界可以将JVM看作一个拥有两种语言的领事馆:一边说 C++(系统语),另一边说 Java(字节码语)。那么 JavaCalls 就是那个身着正装、手里拿着翻译机的首席外交官。 当 JVM 需要从 C++ 内部逻辑(如启动、反射、类初始化)去执行一段 Java 代码时,它必须通过 JavaCalls。
1. 为什么不能直接调用?
在 C++ 里,调用函数遵循的是操作系统的 ABI(应用二进制接口),比如参数放进 RDI, RSI 寄存器。 而在 Java 世界里,方法调用遵循的是 JVM 规范:参数可能压在 Java 栈帧里,或者放进 JVM 自定义的寄存器中。
JavaCalls 的核心任务就是:
2. JavaCalls 的三驾马车
在源码中,你会看到这三个关键类:
- JavaValue:用来接收 Java 方法执行后的返回值。
- JavaCallArguments:一个参数“打包桶”,把 C++ 传递的 int、oop(对象指针)等按顺序装好。
- JavaCalls:执行引擎,包含 call_static、call_virtual、call_special 等方法。
3. 源码深度剖析
JavaCalls 的主要实现在 src/share/vm/runtime/javaCalls.cpp。我们来看最核心的 JavaCalls::call 函数逻辑:
核心代码段 1:参数处理与准备
下面的代码是从call()方法执行过程角度给出的,这些代码是从相关代码合并到一起的。
void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
// 1. 检查方法是否为空,是否已经链接(link)
if (method.is_null()) return;
// 2. 获取目标方法的入口点(可能是解释器入口,也可能是 JIT 编译后的入口)
address entry_point = method->from_interpreted_entry();
// TODO: 待补充真正执行到代码
// 3. 线程状态切换:从 _thread_in_vm 切换到 _thread_in_Java
// 这是为了让 GC 知道,这个线程现在跑的是 Java 代码,不能随便移动它引用的对象
Thread* thread = THREAD;
ThreadStateTransition::transition(thread, _thread_in_vm, _thread_in_Java);
}
以下代码是原始代码(删除部分不相关部分) hotspot\\src\\share\\vm\\runtime\\javaCalls.cpp中call_static()方法涉及的源码
void JavaCalls::call_static(JavaValue* result, KlassHandle klass, Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS) {
CallInfo callinfo;
LinkResolver::resolve_static_call(callinfo, klass, name, signature, KlassHandle(), false, true, CHECK);
methodHandle method = callinfo.selected_method();
// 省略部分代码
// Invoke the method
JavaCalls::call(result, method, args, CHECK);
}
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);
}
hotspot\\src\\share\\vm\\runtime\\javaCalls.cpp中call_helper()方法源码
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
// 省略部分代码
methodHandle method = *m;
JavaThread* thread = (JavaThread*)THREAD;
// 省略部分代码
// Verify the arguments
if (CheckJNICalls) {
args->verify(method, result->get_type(), thread);
}
else debug_only(args->verify(method, result->get_type(), thread));
// Ignore call if method is empty
if (method->is_empty_method()) {
assert(result->get_type() == T_VOID, "an empty method must return a void value");
return;
}
// Since the call stub sets up like the interpreter we call the from_interpreted_entry
// so we can go compiled via a i2c. Otherwise initial entry method will always
// run interpreted.
address entry_point = method->from_interpreted_entry();
// 省略部分代码
// Figure out if the result value is an oop or not (Note: This is a different value
// than result_type. result_type will be T_INT of oops. (it is about size)
BasicType result_type = runtime_type_from(result);
bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);
// NOTE: if we move the computation of the result_val_address inside
// the call to call_stub, the optimizer produces wrong code.
intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
// Find receiver
Handle receiver = (!method->is_static()) ? args->receiver() : Handle();
// 省略部分代码
// 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)
// Restore possible oop return
if (oop_result_flag) {
result->set_jobject((jobject)thread->vm_result());
thread->set_vm_result(NULL);
}
}
核心代码段 2:通过 Call Stub 跳入 Java
这是最精彩的地方。JavaCalls 并不直接 jmp 到 Java 代码,而是通过一个**“跳板” (Call Stub)**。
// 调用底层的 StubRoutines
// 这里使用了函数指针调用,实际上执行的是一段预先生成的汇编代码
StubRoutines::call_stub()(
(address)&link, // 链接器信息
result_address, // 返回值存放处
result_type, // 返回值类型
method(), // Method 对象指针
entry_point, // Java 入口地址
args->parameters(), // 参数列表
args->size_of_parameters(), // 参数个数
CHECK
);
4. 那个神秘的 “Call Stub” 是什么?
StubRoutines::call_stub() 返回的不是 C++ 函数,而是 JVM 在启动时动态生成的一段汇编代码。
这段汇编代码会做以下几件事:
5. 执行流程全景图
当我们调用 System.initializeSystemClass() 时,流程如下:
6. 为什么我们要关心它?
- 性能瓶颈:频繁地在 C++ 和 Java 之间来回切换(跨越 JavaCalls)是有开销的。这就是为什么 JNI 调用如果太频繁会变慢的原因之一。
- 调试意义:如果你在看 core dump(崩溃堆栈),看到堆栈里出现了 StubRoutines 或 JavaCalls::call,你就知道当前程序正处于跨语言调用的临界点。
总结
JavaCalls 就是 JVM 的**“时空门”**。它通过:
完美地解决了底层系统代码与高层逻辑代码之间的隔离与通信。



