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

深入解析HotSpot解释器方法调用机制:从invokevirtual到方法入口

在Java虚拟机中,方法调用是一个复杂但精巧的过程。本文将通过分析OpenJDK 17中HotSpot虚拟机的模板解释器源码,深入探讨方法调用的实现机制,特别是invokevirtual字节码如何最终转换到具体的方法执行。

方法调用的起点:invokevirtual字节码处理

当JVM执行到invokevirtual字节码时,会调用TemplateTable::invokevirtual函数:

cpp

void TemplateTable::invokevirtual(int byte_no) {transition(vtos, vtos);assert(byte_no == f2_byte, "use this argument");prepare_invoke(byte_no,rbx,    // method or vtable indexnoreg,  // unused itable indexrcx, rdx); // recv, flagsinvokevirtual_helper(rbx, rcx, rdx);
}

这里的关键是prepare_invoke函数,它负责为方法调用做准备。让我们看看这个函数的关键部分:

cpp

void TemplateTable::prepare_invoke(int byte_no,Register method,Register index,Register recv,Register flags) {// 保存解释器返回地址__ save_bcp();// 加载调用相关的cp缓存条目load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);// 加载接收者对象(如果需要)if (load_receiver) {__ movl(recv, flags);__ andl(recv, ConstantPoolCacheEntry::parameter_size_mask);const int no_return_pc_pushed_yet = -1;const int receiver_is_at_end = -1;Address recv_addr = __ argument_address(recv, no_return_pc_pushed_yet + receiver_is_at_end);__ movptr(recv, recv_addr);__ verify_oop(recv);}// 计算返回类型和返回地址// ...
}

常量池缓存:加速方法解析的关键

load_invoke_cp_cache_entry函数中,我们可以看到如何从常量池缓存中获取方法信息:

cpp

void TemplateTable::load_invoke_cp_cache_entry(int byte_no,Register method,Register itable_index,Register flags,bool is_invokevirtual,bool is_invokevfinal,bool is_invokedynamic) {const Register cache = rcx;const Register index = rdx;// 解析缓存和索引resolve_cache_and_index(byte_no, cache, index, index_size);// 从缓存加载已解析的方法__ load_resolved_method_at_index(byte_no, method, cache, index);// 加载标志位__ movl(flags, Address(cache, index, Address::times_ptr, flags_offset));
}

这里需要理解的是,cache寄存器不是一个对象,而是一个指向常量池缓存基地址的指针。通过这个指针和索引值,解释器可以快速访问到预先解析好的方法信息。

方法入口:generate_normal_entry

当所有准备工作完成后,执行流程会跳转到目标方法的入口点,即TemplateInterpreterGenerator::generate_normal_entry

cpp

address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) {// 确定代码生成标志bool inc_counter = UseCompiler || CountCompiledCalls || LogTouchedMethods;// ebx: Method*// rbcp: sender spaddress entry_point = __ pc();// 获取参数大小(始终需要)__ movptr(rdx, constMethod);__ load_unsigned_short(rcx, size_of_parameters);// 获取局部变量大小__ load_unsigned_short(rdx, size_of_locals);__ subl(rdx, rcx); // rdx = 额外局部变量数量

在这个入口点,寄存器状态已经设置好:

  • rbx: 包含方法指针(Method*),用于访问方法元数据

  • 栈上: 包含参数和接收者对象(对于实例方法)

值得注意的是,接收者对象(this)并不保存在rcx寄存器中,而是存储在栈上。rcx寄存器被临时用于存储参数大小。

栈帧构建和局部变量初始化

方法入口继续构建栈帧并初始化局部变量:

cpp

// 计算参数起始地址
__ lea(rlocals, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));// 分配和初始化局部变量空间
{Label exit, loop;__ testl(rdx, rdx);__ jcc(Assembler::lessEqual, exit);__ bind(loop);__ push((int) NULL_WORD); // 初始化局部变量__ decrementl(rdx);__ jcc(Assembler::greater, loop);__ bind(exit);
}// 初始化激活帧的固定部分
generate_fixed_frame(false);

方法验证和执行

在开始执行方法体之前,解释器会进行一系列验证:

cpp

// 确保方法不是native和abstract类型
#ifdef ASSERT
__ movl(rax, access_flags);
{Label L;__ testl(rax, JVM_ACC_NATIVE);__ jcc(Assembler::zero, L);__ stop("tried to execute native method as non-native");__ bind(L);
}
// ... 类似地检查抽象方法
#endif

对于同步方法,还会进行额外的处理:

cpp

if (synchronized) {// 分配监视器并锁定方法lock_method();
} else {// 不需要同步// ... 验证代码
}

开始执行方法体

最后,解释器准备好开始执行实际的Java方法代码:

cpp

// jvmti支持
__ notify_method_entry();// 分派下一个指令
__ dispatch_next(vtos);

总结

HotSpot解释器中的方法调用过程是一个精心设计的多阶段过程:

  1. 字节码处理invokevirtual等字节码被解析,方法信息从常量池缓存中加载

  2. 寄存器准备:方法指针存入rbx,接收者对象在栈上准备

  3. 方法入口generate_normal_entry构建栈帧,初始化局部变量

  4. 验证和准备:检查方法属性,处理同步等需求

  5. 执行:通过解释器分派循环执行方法体的字节码

这种设计保证了方法调用的高效性和灵活性,同时维护了Java语义的正确性。理解这一过程对于深入理解JVM内部机制和进行高性能Java程序优化至关重要。

通过分析这些源码,我们不仅了解了方法调用的实现细节,也欣赏到了HotSpot虚拟机工程师在性能和安全之间取得的精巧平衡。

http://www.dtcms.com/a/393180.html

相关文章:

  • 用AI修复失语者的声音:大模型如何帮助渐冻人重新“说话”?
  • 【ICCV 2023】通过学习采样来学习上采样
  • 有效解决舍入误差的方法
  • count down 90 days
  • GEO完全指南 AI时代的内容优化新范式
  • Npass gate transistor是什么器件?
  • TensorRT-LLM中的in-flight batching(IFB)
  • kaggle-NeurIPS - Open Polymer Prediction 2025-0.069
  • Linux基础网络命令
  • CTFHub 密码口令通关笔记:默认密码
  • IPv6 网络连接与 “v6ns“ 测试失败解决方案
  • C++11之move移动语义
  • 【计算机三级备考】真题总结
  • Linux基础指令(上)
  • Protocol Buffers .NET 运行时从核心 API 到工程实战
  • DFT教程 part1 VASP安装与学习推荐
  • 泛函 Φ(u) 驻点所满足的偏微分方程与自然边界条件
  • 基于springboot的健康饮食营养管理系统
  • C语言入门指南:联合体与枚举
  • JS逆向 -去哪儿滑块
  • C++包装器(Wrapper)概述
  • java后端工程师进修ing(研一版‖day47)
  • 小谈:物联网(IoT)与工业传感器技术
  • python标准库
  • 01 Tasking软件安装及新建工程
  • ​​[硬件电路-288]: 单路双输入异或门(门控开关:两个同时为1,输出为1)NC7SZ86L6X 功能概述与管脚定义
  • 声明式事务4
  • 速通ACM省铜第十天 赋源码(A Good Problem和Make It Beautiful和Gellyfish and Baby‘s Breath)
  • 寰宇光锥舟
  • 如何安全的计算softmax?