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

揭秘JVM创世过程之Call Stub进入Java世界的门票

前言

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

前情回顾

在揭秘JVM创世过程之两种语言首席外交官JavaCalls,一文中将JVM看作Java世界中一个拥有两种语言的领事馆:一边说C++(系统语),另一边说Java(字节码语)。那么JavaCalls就是那个身着正装、手里拿着翻译机的首席外交官。当 JVM 需要从 C++ 内部逻辑(如启动、反射、类初始化)去执行一段 Java 代码时,它必须通过JavaCalls。而JavaCalls最精彩的地方并不直接 jmp 到 Java 代码,而是通过一个**“跳板” (Call Stub)**。

JVM 在启动时是如何生成这段“跳板(Call Stub)”汇编代码

进入Java世界的门票“跳板”代码(Call Stub)的生成过程,是 JVM 启动时最硬核的底层操作之一。它不是由编译器提前编译好的,而是 JVM 在运行初期,直接在内存里**“现场手写”**出来的机器码。

这个过程主要由StubGeneratorMacroAssembler这两个组件协作完成。


1. 核心流程:从 C++ 到机器码

Threads::create_vm执行过程中,会调用init_globals(),进而触发stubRoutines_init1()。这时,JVM 会开启“机器码印刷机”。

  • 整个执行过程相关核心代码如下:
  1. hotspot\src\share\vm\runtime\thread.cppThreads::create_vm()核心代码如下:
jintThreads::create_vm(JavaVMInitArgs*args,bool*canTryAgain){// 省略部分代码// Attach the main thread to this os threadJavaThread*main_thread=newJavaThread();main_thread->set_thread_state(_thread_in_vm);main_thread->record_stack_base_and_size();main_thread->initialize_thread_local_storage();main_thread->set_active_handles(JNIHandleBlock::allocate_block());// 省略部分代码// Initialize global modules// 在此方法中会执行stubRoutines_init1(),完成“跳板”代码(Call Stub)jint status=init_globals();if(status!=JNI_OK){deletemain_thread;*canTryAgain=false;// don't let caller call JNI_CreateJavaVM againreturnstatus;}// 省略部分代码{// The VM creates & returns objects of this class. Make sure it's initialized.initialize_class(vmSymbols::java_lang_Class(),CHECK_0);// The VM preresolves methods to these classes. Make sure that they get initializedinitialize_class(vmSymbols::java_lang_reflect_Method(),CHECK_0);initialize_class(vmSymbols::java_lang_ref_Finalizer(),CHECK_0);call_initializeSystemClass(CHECK_0);// 省略部分代码}// 省略部分代码}
  1. init_globals()方法核心代码
    hotspot\src\share\vm\runtime\init.cppinit_globals()中调用stubRoutines_init1()stubRoutines_init2()完成stubRoutines初始化。
jintinit_globals(){// 省略部分代码bytecodes_init();classLoader_init();codeCache_init();VM_Version_init();os_init_globals();stubRoutines_init1();jint status=universe_init();// dependent on codeCache_init and// stubRoutines_init1 and metaspace_init.if(status!=JNI_OK)returnstatus;javaClasses_init();// must happen after vtable initializationstubRoutines_init2();// note: StubRoutines need 2-phase init// 省略部分代码returnJNI_OK;}
  1. stubRoutines_init1()方法核心代码

hotspot\src\share\vm\runtime\stubRoutines.cpp

voidstubRoutines_init1(){StubRoutines::initialize1();}voidStubRoutines::initialize1(){if(_code1==NULL){ResourceMark rm;TraceTimetimer("StubRoutines generation 1",TraceStartupTime);_code1=BufferBlob::create("StubRoutines (1)",code_size1);if(_code1==NULL){vm_exit_out_of_memory(code_size1,OOM_MALLOC_ERROR,"CodeCache: no room for StubRoutines (1)");}CodeBufferbuffer(_code1);StubGenerator_generate(&buffer,false);}}
Step 1: 申请可执行内存 (Code Buffer)

JVM 会在内存中开辟一块特殊的区域(属于 CodeCache 的一部分),并将其权限设置为可读、可写、可执行(RWX)。这块内存就是用来存放生成的汇编指令的“纸”。

hotspot\src\share\vm\runtime\stubRoutines.cpp在方法initialize1()中通过BufferBlob::create()申请一块内存,代码如下:

voidStubRoutines::initialize1(){if(_code1==NULL){ResourceMark rm;TraceTimetimer("StubRoutines generation 1",TraceStartupTime);_code1=BufferBlob::create("StubRoutines (1)",code_size1);if(_code1==NULL){vm_exit_out_of_memory(code_size1,OOM_MALLOC_ERROR,"CodeCache: no room for StubRoutines (1)");}CodeBufferbuffer(_code1);StubGenerator_generate(&buffer,false);}}
Step 2: 启动 MacroAssembler (宏汇编器)

JVM 使用一个名为MacroAssembler的 C++ 类。这个类非常神奇,它把每一条 CPU 指令(如push,mov,call)都封装成了一个 C++ 函数。

  • 当你调用masm->push(rax)时,它并不是在执行 push,而是在往刚才申请的内存缓冲区里写入0x50(x86-64 架构下push rax的机器码)。

2. 生成 Call Stub 的具体代码逻辑

在 OpenJDK 源码中(以 x86_64 为例),生成这段代码的逻辑位于src/hotspot/cpu/x86/stubGenerator_x86_64.cppgenerate_call_stub方法中。

我们可以通过 C++ 源码窥见它“手写”汇编的过程:

addressgenerate_call_stub(address&return_address){// ... 准备缓冲区 ...StubCodeMarkmark(this,"StubRoutines","call_stub");address start=__pc();// 记录起始地址// 1. 保存调用者(C++)的现场__enter();// push rbp; mov rbp, rsp__push(r15);// 保存关键寄存器__push(r14);__push(r13);__push(r12);// 2. 将 C++ 传进来的参数移动到指定的寄存器// 比如从 c_rarg1 (rsi) 拿到 Java 方法的入口地址__movptr(rbx,method);// 把 Method* 放入 rbx// 3. 构建 Java 栈帧// 这里会根据参数个数动态调整 rsp 的位置,为 Java 参数腾地方// 4. 关键一跃:跳入 Java 世界__call(entry_point);// 这里的 entry_point 就是解释器的入口// 5. 凯旋归来:清理 Java 栈并恢复 C++ 寄存器__pop(r12);__pop(r13);__pop(r14);__pop(r15);__leave();__ret(0);// 返回到 JavaCalls::callreturnstart;// 返回这段代码在内存中的首地址}

3. 如何变成函数指针?

生成完这段机器码后,JVM 会做一个非常关键的动作:

  1. 记录地址:将start(这段代码的首地址)存入全局变量StubRoutines::_call_stub_entry

  2. 强制转换:在头文件中,这个地址被定义为一个复杂的函数指针类型。

  3. 调用:当JavaCalls需要执行 Java 代码时,它直接通过这个指针调用:

(*(CallStub)cast_to_fqn(_call_stub_entry))(...)

这时,CPU 就会从当前的 C++ 指令流,直接跳转到那块刚才生成的内存区域,开始执行那几条pushmov


4. 为什么要“现场手写”而不是写在.s汇编文件里?

你可能会问:为什么不直接写个.s汇编文件编译进来?

  • 动态性:JVM 需要根据当前的硬件特性(比如是否支持 AVX-512 指令集、是否开启了某些安全补丁)来动态决定生成什么样的指令。
  • 极致优化:现场生成可以根据当前的偏移量计算出最短的跳转指令(Short Jump),减少代码体积。
  • 统一抽象:通过 C++ 宏汇编器,JVM 可以用一套逻辑在不同的系统(Linux/Windows/macOS)上生成对应的机器码,而不需要维护无数个版本的.s文件。

总结

JVM 生成跳板代码的过程,就像是一个在施工现场直接烧制异形砖块的建筑师

  1. MacroAssembler是模具。
  2. 内存缓冲区是原材料。
  3. StubGenerator是施工图纸。

这种“运行时生成代码”的技术(JIT 思想的萌芽),确保了 Java 能够跨越 C++ 和字节码的鸿沟,同时保持顶级的执行效率。

http://www.jsqmd.com/news/572909/

相关文章:

  • 实测Qwen3.5推理模型:用它写代码、解逻辑题,效果到底有多强?
  • ubuntu秘钥生成PKCS1 格式秘钥
  • Gemma-3-270m多场景应用:律师合同风险点识别、条款合规性初筛案例
  • PyTorch 2.8镜像实操手册:/data盘挂载后权限配置与数据安全策略
  • 钢链数智,赋能实业——千匠网络钢铁产业电商系统,破解行业困局,激活钢铁增长新动能
  • Odoo 19成本核算避坑指南:标准成本法下差异分析、委外加工汇率风险与WIP分录丢失问题
  • 3步掌握百度网盘效率工具:全平台秒传链接解决方案
  • 如何用1000美元打造工业级六轴机械臂:Faze4开源项目的完整实践指南
  • 解锁3大智能功能:League-Toolkit让普通玩家也能玩转专业级游戏分析
  • 大模型文件的组成
  • 51单片机实战:从零构建电子密码锁系统
  • ai辅助开发,让快马平台智能优化你的openclaw脚本安全性与性能
  • 安全打穿查重黑盒!2026论文降AI全攻略:权威提示词集实录 x 3款工具基准测试
  • 5步打造专业音乐播放器:foobox-cn界面美化终极指南
  • 第八届题目
  • 云容笔谈效果对比评测: vs Stable Diffusion 3.5东方人像生成质量深度分析
  • 什么是推理引擎
  • 基于S7-300与组态王的智能药片装瓶机控制系统优化设计
  • 电源管理入门-13Thermal 热管理
  • 进制转换题
  • 马年市场快报分析:欧美组合式一氧化碳及可燃气体报警器指南
  • 从二进制到汇编:用hello_world.o揭秘程序在内存中的真实模样
  • 若依框架实战:如何优雅地实现静态资源权限校验(附完整代码)
  • 手把手体验Palantir AIP:用官方Demo教程,5步构建一个供应链风险AI预警应用
  • XML、JAXB(嵌套类等)的复杂序列化
  • FreeRTOS实战:如何用TIM2定时器精准统计任务运行时间(附完整代码)
  • 避坑指南:AI面相手相源码搭建中的5个常见问题及解决方案(附虚拟人数设置技巧)
  • 3个革命性技巧:用PyMC-Marketing实现数据驱动的营销决策
  • win11新机器设置杂七杂八
  • SaaS的末日重构:AI Agent浪潮下的危机与新生