dispatch_next()方法的实现

之前的文章介绍到,在generate_normal_entry()函数中会调用generate_fixed_frame()函数为Java方法的执行生成对应的栈帧,接下来还会调用dispatch_next()函数执行Java方法的字节码。generate_normal_entry()函数中调用的dispatch_next()函数的实现如下:

// 从generate_fixed_frame()函数生成固定桢的时候,如果当前是第一次调用,
// 那么r13指向的是字节码的首地址,即第一个字节码,而step为0。
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {

  // load next bytecode (load before advancing r13 to prevent AGI)
  load_unsigned_byte(rbx, Address(r13, step)); 

  // 在当前字节码的位置,指针向前移动step宽度,获取地址上的值,这个值即为字节码在转发表中的index,
  // 存储到rbx。step的值由字节码指令和操作数决定。转发表中的index其实就是字节码(范围1~202),
  // 参考void DispatchTable::set_entry(int i, EntryPoint& entry) 方法。 
  // advance r13 
  increment(r13, step);//自增r13供下一次dispatch使用

  // 返回当前栈顶状态的所有字节码入口点
  dispatch_base(state, Interpreter::dispatch_table(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;  // the active    dispatch table (used by the interpreter for dispatch)

DispatchTable类及table_for()等方法的定义如下:

DispatchTable  TemplateInterpreter::_active_table;

class DispatchTable VALUE_OBJ_CLASS_SPEC {
 public:
  // an entry point for each byte value (also for undefined bytecodes)
  enum { length = 1 << BitsPerByte }; // BitsPerByte的值为8
 
 private:
  // dispatch tables, indexed by tosca and bytecode
  address  _table[number_of_states][length];   // number_of_states=9,length=256
 
 public:
  // Attributes
  // ...
  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));
} 

比如取一个字节的指令,那么InterpreterMacroAssembler::dispatch_next()函数生成的汇编代码如下 :

// 在generate_fixed_frame()方法中已经让%r13存储了bcp
0x00007fffe1010643: movzbl 0x0(%r13),%ebx    // %ebx中存储的是字节码的操作码
// $0x7ffff73ba4a0这个地址指向的是对应state状态下的一维数组,长度为256
0x00007fffe1010648: movabs $0x7ffff73ba4a0,%r10
// 注意%r10中存储的是常量,根据计算公式%r10+%rbx*8来获取指向存储入口地址的地址,
// 通过*(%r10+%rbx*8)获取到入口地址,然后跳转到入口地址执行
0x00007fffe1010652: 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/jvms-7.html

调用dispatch_next()函数执行Java方法的字节码,其实就是根据字节码找到对应的入口地址来执行,而入口地址就是机器码的入口地址,这个机器码就是根据对应的字节码翻译过来的,这些都会在后面详细介绍。

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码 

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程 

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)  

7、HotSpot的类模型(3) 

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)  

10、HotSpot的对象模型(6) 

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器 

14、类的双亲委派机制 

15、核心类的预装载

16、Java主类的装载  

17、触发类的装载  

18、类文件介绍 

19、文件流 

20、解析Class文件 

21、常量池解析(1) 

22、常量池解析(2)

23、字段解析(1)

24、字段解析之伪共享(2) 

25、字段解析(3)  

26、字段解析之OopMapBlock(4)

27、方法解析之Method与ConstMethod介绍  

28、方法解析

29、klassVtable与klassItable类的介绍  

30、计算vtable的大小 

31、计算itable的大小 

32、解析Class文件之创建InstanceKlass对象 

33、字段解析之字段注入 

34、类的连接  

35、类的连接之验证 

36、类的连接之重写(1) 

37、类的连接之重写(2)

38、方法的连接  

39、初始化vtable 

40、初始化itable  

41、类的初始化 

42、对象的创建  

43、Java引用类型 

44、Java引用类型之软引用(1)

45、Java引用类型之软引用(2)

46、Java引用类型之弱引用与幻像引用  

47、Java引用类型之最终引用

48、HotSpot的垃圾回收算法  

49、HotSpot的垃圾回收器   

50、CallStub栈帧 

51、entry point栈帧  

52、generate_fixed_frame()方法生成Java方法栈帧 

作者持续维护的个人博客  classloading.com

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

   

参考:

(1)HotSpot模板解释器目标代码生成过程源码分析 

(2)generate_normal_entry()方法中调用的lock_method()方法的解释参考: 

(3)entry_point–JVM Java栈桢的创建

原文地址:https://www.cnblogs.com/mazhimazhi/p/13518830.html