第8篇-dispatch_next()函数分派字节码
在generate_normal_entry()函数中会调用generate_fixed_frame()函数为Java方法的执行生成对应的栈帧,接下来还会调用dispatch_next()函数执行Java方法的字节码。generate_normal_entry()函数调用的dispatch_next()函数之前一些寄存器中保存的值如下:
rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的地址
r14:本地变量表第1个参数的地址
dispatch_next()函数的实现如下:
// 从generate_fixed_frame()函数生成Java方法调用栈帧的时候,
// 如果当前是第一次调用,那么r13指向的是字节码的首地址,
// 即第一个字节码,此时的step参数为0
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) { load_unsigned_byte(rbx, Address(r13, step)); // 在当前字节码的位置,指针向前移动step宽度,
// 获取地址上的值,这个值是Opcode(范围1~202),存储到rbx
// step的值由字节码指令和它的操作数共同决定
// 自增r13供下一次字节码分派使用
increment(r13, step); // 返回当前栈顶状态的所有字节码入口点
dispatch_base(state, Interpreter::dispatch_table(state));
}
r13指向字节码的首地址,当第1次调用时,参数step的值为0,那么load_unsigned_byte()函数从r13指向的内存中取一个字节的值,取出来的是字节码指令的操作码。增加r13的步长,这样下次执行时就会取出来下一个字节码指令的操作码。
调用的dispatch_table()函数的实现如下:
static address* dispatch_table(TosState state) {
return _active_table.table_for(state);
}
在_active_table中获取对应栈顶缓存状态的入口地址,_active_table变量定义在TemplateInterpreter类中,如下:
static DispatchTable _active_table;
DispatchTable类及table_for()等函数的定义如下:
DispatchTable TemplateInterpreter::_active_table;
class DispatchTable VALUE_OBJ_CLASS_SPEC {
public:
enum {
length = 1 << BitsPerByte
}; // BitsPerByte的值为8
private:
// number_of_states=9,length=256
// _table是字节码分发表
address _table[number_of_states][length];
public:
// ...
address* table_for(TosState state){
return _table[state];
}
address* table_for(){
return table_for((TosState)0);
}
// ...
};
address为u_char*类型的别名。_table是一个二维数组的表,维度为栈顶状态(共有9种)和字节码(最多有256个),存储的是每个栈顶状态对应的字节码的入口点。这里由于还没有介绍栈顶缓存,所以理解起来并不容易,不过后面会详细介绍栈顶缓存和字节码分发表的相关内容,等介绍完了再看这部分逻辑就比较容易理解了。
InterpreterMacroAssembler::dispatch_next()函数中调用的dispatch_base()函数的实现如下:
void InterpreterMacroAssembler::dispatch_base(
TosState state, // 表示栈顶缓存状态
address* table,
bool verifyoop
) {
// ...
// 获取当前栈顶状态字节码转发表的地址,保存到rscratch1
lea(rscratch1, ExternalAddress((address)table));
// 跳转到字节码对应的入口执行机器码指令
// address = rscratch1 + rbx * 8
jmp(Address(rscratch1, rbx, Address::times_8));
}
比如取一个字节大小的指令(如iconst_0、aload_0等都是一个字节大小的指令),那么InterpreterMacroAssembler::dispatch_next()函数生成的汇编代码如下 :
// 在generate_fixed_frame()函数中
// 已经让%r13存储了bcp
// %ebx中存储的是字节码的Opcode,也就是操作码
movzbl 0x0(%r13),%ebx // $0x7ffff73ba4a0这个地址指向的
// 是对应state状态下的一维数组,长度为256
movabs $0x7ffff73ba4a0,%r10 // 注意%r10中存储的是常量,根据计算公式
// %r10+%rbx*8来获取指向存储入口地址的地址,
// 通过*(%r10+%rbx*8)获取到入口地址,
// 然后跳转到入口地址执行
jmpq *(%r10,%rbx,8)
%r10指向的是对应栈顶缓存状态state下的一维数组,长度为256,其中存储的值为opcode,如下图所示。

下面的函数显示了对每个字节码的每个栈顶状态都设置入口地址。
void DispatchTable::set_entry(int i, EntryPoint& entry) {
assert(0 <= i && i < length, "index out of bounds");
assert(number_of_states == 9, "check the code below");
_table[btos][i] = entry.entry(btos);
_table[ctos][i] = entry.entry(ctos);
_table[stos][i] = entry.entry(stos);
_table[atos][i] = entry.entry(atos);
_table[itos][i] = entry.entry(itos);
_table[ltos][i] = entry.entry(ltos);
_table[ftos][i] = entry.entry(ftos);
_table[dtos][i] = entry.entry(dtos);
_table[vtos][i] = entry.entry(vtos);
}
其中的参数i就是opcode,各个字节码及对应的opcode可参考https://docs.oracle.com/javase/specs/jvms/se8/html/index.html。
所以_table表如下图所示。

_table的一维为栈顶缓存状态,二维为Opcode,通过这2个维度能够找到一段机器指令,这就是根据当前的栈顶缓存状态定位到的字节码需要执行的机器指令片段。
调用dispatch_next()函数执行Java方法的字节码,其实就是根据字节码找到对应的机器指令片段的入口地址来执行,这段机器码就是根据对应的字节码语义翻译过来的,这些都会在后面详细介绍。
推荐阅读:
第2篇-JVM虚拟机这样来调用Java主类的main()方法
如果有问题可直接评论留言或加作者微信mazhimazh
关注公众号,有HotSpot源码剖析系列文章!

第8篇-dispatch_next()函数分派字节码的更多相关文章
- [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析
[WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...
- android apk 防止反编译技术第二篇-运行时修改字节码
上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止a ...
- 内置函数 -- bytes -- 字节码与字符串相互转换
说明: 1. 返回值为一个新的不可修改字节数组,每个数字元素都必须在0 - 255范围内,是bytearray函数的具有相同的行为,差别仅仅是返回的字节数组不可修改. 2. 当3个参数都不传的时候,返 ...
- [WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建
看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的 ...
- 第32篇-解析interfacevirtual字节码指令
在前面介绍invokevirtual指令时,如果判断出ConstantPoolCacheEntry中的_indices字段的_f2属性的值为空,则认为调用的目标方法没有连接,也就是没有向Constan ...
- 第34篇-解析invokeinterface字节码指令
与invokevirtual指令类似,当没有对目标方法进行解析时,需要调用LinkResolver::resolve_invoke()函数进行解析,这个函数会调用其它一些函数完成方法的解析,如下图所示 ...
- [原创]ASM动态修改JAVA函数之函数字节码初探
ASM是非常强大的JAVA字节码生成和修改工具,具有性能优异.文档齐全.比较易用等优点.官方网站:http://asm.ow2.org/ 要想熟练的使用ASM,需要对java字节码有一定的了解,本文重 ...
- Python_Tips[2] -> 函数延后估值及字节码分析
函数延后估值及字节码分析 在一个循环中定义了函数 f 但是并未对其进行调用,在循环结束后调用,此时i值为3故最终3个函数输出均为9.而非1, 4, 9. 这是由于在定义闭包函数 f 时,传入变量 i, ...
- 大话+图说:Java字节码指令——只为让你懂
前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...
随机推荐
- java -jar 运行springboot项目时内存设置
java -Xms64m #JVM启动时的初始堆大小 -Xmx128m #最大堆大小 -Xmn64m #年轻代的大小,其余的空间是老年代 -XX:MaxMetaspaceSize=128m # -XX ...
- C语言中的.h和.c文件
1.h为头文件,.c为源文件,其实两者都是代码,没有实质性的区别,只是后缀不一样,是一种编程规范,主要是为了解决定义与调用之间的混乱. 2.h文件一般写一些函数声明.宏定义.结构体等内容:c文件是程序 ...
- Kotlin Coroutine(协程): 四、+ Retrofit
@ 目录 前言 一.准备工作 二.开始使用 1.简单使用 2.DSL 3.扩展函数 4.请求发起 总结 前言 Retrofit 从 2.6.0 版本开始, 内置了对 Kotlin Coroutines ...
- ARTS第一周
开始进行的第一周. 1.Algorithm:每周至少做一个 leetcode 的算法题2.Review:阅读并点评至少一篇英文技术文章3.Tip:学习至少一个技术技巧4.Share:分享一篇有观点和思 ...
- [NOI2000] 古城之谜
题目描述 给定 n 和 n 个信息,每个信息包含一个词性 a (只有三种:名,动,辅)和对应的词 mot ,形为" \(a.mot\) ".(一次可能多词性) 最后给一个长度不大于 ...
- PYTHON 错误提示:ModuleNotFoundError: No module named 'cv2'
ModuleNotFoundError: No module named 'cv2' 解决方法: pip install -i https://pypi.tuna.tsinghua.edu.cn/si ...
- [刘阳Java]_SpringMVC访问静态资源_第9讲
有些时候我们在使用SpringMVC的时候造成无法访问静态资源文件(如:html,js,css,image等等).其主要的原因出在web.xml文件我们设置SpringMVC前端控制器的映射路径 &l ...
- python之数据驱动ddt操作(方法四)
from ddt import ddt,data,unpackfrom selenium import webdriverfrom selenium.webdriver.common.by impor ...
- 【LeetCode】94. 二叉树的中序遍历
94. 二叉树的中序遍历 知识点:二叉树:递归:Morris遍历 题目描述 给定一个二叉树的根节点 root ,返回它的 中序 遍历. 示例 输入:root = [1,null,2,3] 输出:[1, ...
- centos7下安装mycat中间件 笔记
1. 下载 # wget http://dl.mycat.org.cn/1.6.7.4/Mycat-server-1.6.7.4-release/Mycat-server-1.6.7.4-releas ...