在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方法的字节码,其实就是根据字节码找到对应的机器指令片段的入口地址来执行,这段机器码就是根据对应的字节码语义翻译过来的,这些都会在后面详细介绍。

推荐阅读:

第1篇-关于JVM运行时,开篇说的简单些

第2篇-JVM虚拟机这样来调用Java主类的main()方法

第3篇-CallStub新栈帧的创建

第4篇-JVM终于开始调用Java主类的main()方法啦

第5篇-调用Java方法后弹出栈帧及处理返回结果

第6篇-Java方法新栈帧的创建

第7篇-为Java方法创建栈帧

如果有问题可直接评论留言或加作者微信mazhimazh

关注公众号,有HotSpot源码剖析系列文章!

第8篇-dispatch_next()函数分派字节码的更多相关文章

  1. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  2. android apk 防止反编译技术第二篇-运行时修改字节码

    上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止a ...

  3. 内置函数 -- bytes -- 字节码与字符串相互转换

    说明: 1. 返回值为一个新的不可修改字节数组,每个数字元素都必须在0 - 255范围内,是bytearray函数的具有相同的行为,差别仅仅是返回的字节数组不可修改. 2. 当3个参数都不传的时候,返 ...

  4. [WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建

    看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的 ...

  5. 第32篇-解析interfacevirtual字节码指令

    在前面介绍invokevirtual指令时,如果判断出ConstantPoolCacheEntry中的_indices字段的_f2属性的值为空,则认为调用的目标方法没有连接,也就是没有向Constan ...

  6. 第34篇-解析invokeinterface字节码指令

    与invokevirtual指令类似,当没有对目标方法进行解析时,需要调用LinkResolver::resolve_invoke()函数进行解析,这个函数会调用其它一些函数完成方法的解析,如下图所示 ...

  7. [原创]ASM动态修改JAVA函数之函数字节码初探

    ASM是非常强大的JAVA字节码生成和修改工具,具有性能优异.文档齐全.比较易用等优点.官方网站:http://asm.ow2.org/ 要想熟练的使用ASM,需要对java字节码有一定的了解,本文重 ...

  8. Python_Tips[2] -> 函数延后估值及字节码分析

    函数延后估值及字节码分析 在一个循环中定义了函数 f 但是并未对其进行调用,在循环结束后调用,此时i值为3故最终3个函数输出均为9.而非1, 4, 9. 这是由于在定义闭包函数 f 时,传入变量 i, ...

  9. 大话+图说:Java字节码指令——只为让你懂

    前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...

随机推荐

  1. Spring:Spring中bean的生命周期

    Spring中,从BeanFactory或ApplicationContext取得的实例为Singleton(单例模式),就是预设为每一个Bean的别名只能维持一个实例,而不是每次都产生一个新的对象使 ...

  2. springboot 使用yml配置文件自定义属性

    springboot 中在application.yml文件里自定义属性值,配合@Value注解可以在代码中直接取到相应的值,如在application.yml中添加 mqtt: serverURI: ...

  3. pdfkit html转pdf

    pdfkit的通用option选项 参考:https://cloud.tencent.com/developer/ask/202116https://www.cnblogs.com/taceywong ...

  4. mqtt 集成

    -- 在pom.xml导入依赖 <!-- mqtt --> <dependency> <groupId>org.springframework.boot</g ...

  5. Linux守护进程列表/守护进程

      在linux或者unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统. ...

  6. 链表逆序---python

    class ListNode: Value = '' # 节点要储存的值,因为Python是弱类型,因此无需传入泛型 Next = None # 下一个节点,初始化时为空值 def __init__( ...

  7. cke编辑器插入&ZeroWidthSpace占位字符的问题记录

    背景 本博文主要记录在使用cke编辑器时,遇到的一系列的问题 问题1:在执行某些业务操作后,编辑器会偶现在页面头部或者尾部插入&ZeroWidthSpace占位符(编辑器好像就爱干这事~) 解 ...

  8. java基础---设计模式(2)

    结构型模式 出处:https://blog.csdn.net/zhangerqing/article/details/8239539 一.适配器模式 适配器模式将某个类的接口转换成客户端期望的另一个接 ...

  9. python with (as)语句

    with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的"清理"操作,释放资源,比如文件使用后自动关闭.线程中锁的自动获取和释放等. 例1:url = ...

  10. C语言的编译与链接

    C语言源文件要经过编译.链接才能生成可执行程序:编译(Compile)会将源文件(.c文件)转换为目标文件.对于 VC/VS,目标文件后缀为.obj:对于GCC,目标文件后缀为.o.编译是针对单个源文 ...