Java的模板解析执行需要模板表与转发表的支持,而这2个表中的数据在HotSpot虚拟机启动时就会初始化。这一篇首先介绍模板表。
在启动虚拟机阶段会调用init_globals()方法初始化全局模块,在这个方法中通过调用interpreter_init()方法初始化模板解释器,调用栈如下:
TemplateInterpreter::initialize() templateInterpreter.cpp interpreter_init() interpreter.cpp init_globals() init.cpp Threads::create_vm() thread.cpp JNI_CreateJavaVM() jni.cpp InitializeJVM() java.c JavaMain() java.c start_thread() pthread_create.c
interpreter_init()方法主要是通过调用TemplateInterpreter::initialize()方法来完成逻辑,initialize()方法的实现如下:
源代码位置:/src/share/vm/interpreter/templateInterpreter.cpp void TemplateInterpreter::initialize() { if (_code != NULL) return; // 抽象解释器AbstractInterpreter的初始化,AbstractInterpreter是基于汇编模型的解释器的共同基类, // 定义了解释器和解释器生成器的抽象接口 AbstractInterpreter::initialize(); // 模板表TemplateTable的初始化,模板表TemplateTable保存了各个字节码的模板 TemplateTable::initialize(); // generate interpreter { ResourceMark rm; int code_size = InterpreterCodeSize; // CodeCache的Stub队列StubQueue的初始化 _code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,"Interpreter"); // 实例化模板解释器生成器对象TemplateInterpreterGenerator InterpreterGenerator g(_code); } // initialize dispatch table _active_table = _normal_table; }
模板解释器的初始化包括如下几个方面:
(1)抽象解释器AbstractInterpreter的初始化,AbstractInterpreter是基于汇编模型的解释器的共同基类,定义了解释器和解释器生成器的抽象接口。
(2)模板表TemplateTable的初始化,模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数);
(3)CodeCache的Stub队列StubQueue的初始化;
(4)解释器生成器InterpreterGenerator的初始化。
在之前介绍过,在TemplateInterpreter::initialize() 中通过调用语句来间接调用generate_method_entry()和generate_normal_entry()创建方法执行的栈帧:
InterpreterGenerator g(_code);
不过在如上语句调用之前,首先需要调用TemplateInterpreter类中的initialize()方法初始化模板表,如下:
TemplateTable::initialize();
模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数),initialize()方法的实现如下:
源代码位置:/src/share/vm/interpreter/templateInterpreter.cpp
void TemplateTable::initialize() { if (_is_initialized) return; _bs = Universe::heap()->barrier_set(); // For better readability const char _ = ' '; const int ____ = 0; const int ubcp = 1 << Template::uses_bcp_bit; const int disp = 1 << Template::does_dispatch_bit; const int clvm = 1 << Template::calls_vm_bit; const int iswd = 1 << Template::wide_bit; // interpr. templates // Java spec bytecodes ubcp|disp|clvm|iswd in out generator argument def(Bytecodes::_nop , ____|____|____|____, vtos, vtos, nop , _ ); def(Bytecodes::_aconst_null , ____|____|____|____, vtos, atos, aconst_null , _ ); def(Bytecodes::_iconst_m1 , ____|____|____|____, vtos, itos, iconst , -1 ); def(Bytecodes::_iconst_0 , ____|____|____|____, vtos, itos, iconst , 0 ); // ... def(Bytecodes::_tableswitch , ubcp|disp|____|____, itos, vtos, tableswitch , _ ); def(Bytecodes::_lookupswitch , ubcp|disp|____|____, itos, itos, lookupswitch , _ ); def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos ); def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos ); def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos ); def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos ); def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos ); def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos ); def(Bytecodes::_getstatic , ubcp|____|clvm|____, vtos, vtos, getstatic , f1_byte ); def(Bytecodes::_putstatic , ubcp|____|clvm|____, vtos, vtos, putstatic , f2_byte ); def(Bytecodes::_getfield , ubcp|____|clvm|____, vtos, vtos, getfield , f1_byte ); def(Bytecodes::_putfield , ubcp|____|clvm|____, vtos, vtos, putfield , f2_byte ); def(Bytecodes::_invokevirtual , ubcp|disp|clvm|____, vtos, vtos, invokevirtual , f2_byte ); def(Bytecodes::_invokespecial , ubcp|disp|clvm|____, vtos, vtos, invokespecial , f1_byte ); def(Bytecodes::_invokestatic , ubcp|disp|clvm|____, vtos, vtos, invokestatic , f1_byte ); def(Bytecodes::_invokeinterface , ubcp|disp|clvm|____, vtos, vtos, invokeinterface , f1_byte ); def(Bytecodes::_invokedynamic , ubcp|disp|clvm|____, vtos, vtos, invokedynamic , f1_byte ); def(Bytecodes::_new , ubcp|____|clvm|____, vtos, atos, _new , _ ); def(Bytecodes::_newarray , ubcp|____|clvm|____, itos, atos, newarray , _ ); def(Bytecodes::_anewarray , ubcp|____|clvm|____, itos, atos, anewarray , _ ); def(Bytecodes::_arraylength , ____|____|____|____, atos, itos, arraylength , _ ); def(Bytecodes::_athrow , ____|disp|____|____, atos, vtos, athrow , _ ); def(Bytecodes::_checkcast , ubcp|____|clvm|____, atos, atos, checkcast , _ ); def(Bytecodes::_instanceof , ubcp|____|clvm|____, atos, itos, instanceof , _ ); def(Bytecodes::_monitorenter , ____|disp|clvm|____, atos, vtos, monitorenter , _ ); def(Bytecodes::_monitorexit , ____|____|clvm|____, atos, vtos, monitorexit , _ ); def(Bytecodes::_wide , ubcp|disp|____|____, vtos, vtos, wide , _ ); def(Bytecodes::_multianewarray , ubcp|____|clvm|____, vtos, atos, multianewarray , _ ); def(Bytecodes::_ifnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , equal ); def(Bytecodes::_ifnonnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , not_equal ); def(Bytecodes::_goto_w , ubcp|____|clvm|____, vtos, vtos, goto_w , _ ); def(Bytecodes::_jsr_w , ubcp|____|____|____, vtos, vtos, jsr_w , _ ); // wide Java spec bytecodes def(Bytecodes::_iload , ubcp|____|____|iswd, vtos, itos, wide_iload , _ ); def(Bytecodes::_lload , ubcp|____|____|iswd, vtos, ltos, wide_lload , _ ); // ... // JVM bytecodes def(Bytecodes::_fast_agetfield , ubcp|____|____|____, atos, atos, fast_accessfield , atos ); def(Bytecodes::_fast_bgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_cgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_dgetfield , ubcp|____|____|____, atos, dtos, fast_accessfield , dtos ); def(Bytecodes::_fast_fgetfield , ubcp|____|____|____, atos, ftos, fast_accessfield , ftos ); def(Bytecodes::_fast_igetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_lgetfield , ubcp|____|____|____, atos, ltos, fast_accessfield , ltos ); def(Bytecodes::_fast_sgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_aputfield , ubcp|____|____|____, atos, vtos, fast_storefield , atos ); def(Bytecodes::_fast_bputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_cputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_dputfield , ubcp|____|____|____, dtos, vtos, fast_storefield , dtos ); def(Bytecodes::_fast_fputfield , ubcp|____|____|____, ftos, vtos, fast_storefield , ftos ); def(Bytecodes::_fast_iputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_lputfield , ubcp|____|____|____, ltos, vtos, fast_storefield , ltos ); def(Bytecodes::_fast_sputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_aload_0 , ____|____|____|____, vtos, atos, aload , 0 ); def(Bytecodes::_fast_iaccess_0 , ubcp|____|____|____, vtos, itos, fast_xaccess , itos ); def(Bytecodes::_fast_aaccess_0 , ubcp|____|____|____, vtos, atos, fast_xaccess , atos ); def(Bytecodes::_fast_faccess_0 , ubcp|____|____|____, vtos, ftos, fast_xaccess , ftos ); def(Bytecodes::_fast_iload , ubcp|____|____|____, vtos, itos, fast_iload , _ ); def(Bytecodes::_fast_iload2 , ubcp|____|____|____, vtos, itos, fast_iload2 , _ ); def(Bytecodes::_fast_icaload , ubcp|____|____|____, vtos, itos, fast_icaload , _ ); def(Bytecodes::_fast_invokevfinal , ubcp|disp|clvm|____, vtos, vtos, fast_invokevfinal , f2_byte ); def(Bytecodes::_fast_linearswitch , ubcp|disp|____|____, itos, vtos, fast_linearswitch , _ ); def(Bytecodes::_fast_binaryswitch , ubcp|disp|____|____, itos, vtos, fast_binaryswitch , _ ); def(Bytecodes::_fast_aldc , ubcp|____|clvm|____, vtos, atos, fast_aldc , false ); def(Bytecodes::_fast_aldc_w , ubcp|____|clvm|____, vtos, atos, fast_aldc , true ); def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return , vtos ); def(Bytecodes::_invokehandle , ubcp|disp|clvm|____, vtos, vtos, invokehandle , f1_byte ); def(Bytecodes::_shouldnotreachhere , ____|____|____|____, vtos, vtos, shouldnotreachhere , _ ); // platform specific bytecodes pd_initialize(); _is_initialized = true; }
TemplateTable的初始化调用def()将所有字节码的目标代码生成函数和参数保存在_template_table或_template_table_wide(wide指令)模板数组中。除了虚拟机规范本身定义的字节码指令外,HotSpot虚拟机也定义了一些字节码指令,这些指令为了辅助虚拟机进行更好、理简单的功能实现,例如Bytecodes::_return_register_finalizer等在之前已经介绍过,可以更好的实现finalizer类型对象的注册功能。
对于调用def()函数时传递的一些参数在后面会解释。def()函数有2个,接收的参数不同,实现如下:
void TemplateTable::def( Bytecodes::Code code, // 字节码指令 int flags, // 标志位 TosState in, // 模板执行前TosState TosState out, // 模板执行后TosState void (*gen)(), // 模板生成器,是模板的核心组件 char filler ) { assert(filler == ' ', "just checkin'"); def(code, flags, in, out, (Template::generator)gen, 0); // 调用下面的def()函数 } void TemplateTable::def( Bytecodes::Code code, // 字节码指令 int flags, // 标志位 TosState in, // 模板执行前TosState TosState out, // 模板执行后TosState void (*gen)(int arg), // 模板生成器,是模板的核心组件 int arg ) { // should factor out these constants const int ubcp = 1 << Template::uses_bcp_bit; // 表示是否需要bcp指针 const int disp = 1 << Template::does_dispatch_bit; // 表示是否在模板范围内进行转发 const int clvm = 1 << Template::calls_vm_bit; // 表示是否需要调用JVM函数 const int iswd = 1 << Template::wide_bit; // 表示是否为wild指令 // determine which table to use bool is_wide = (flags & iswd) != 0; // make sure that wide instructions have a vtos entry point // (since they are executed extremely rarely, it doesn't pay out to have an // extra set of 5 dispatch tables for the wide instructions - for simplicity // they all go with one table) assert(in == vtos || !is_wide, "wide instructions have vtos entry point only"); Template* t = is_wide ? template_for_wide(code) : template_for(code); // setup entry t->initialize(flags, in, out, gen, arg); // 调用模板表t的initialize()方法初始化模板表 assert(t->bytecode() == code, "just checkin'"); }
模板表由模板表数组与一组生成器组成:
(1)模板表数组有_template_table与_template_table_wild,数组的下标为bytecode,值为Template,按照字节码指令的操作码递增顺序排列。
(2)一组生成器,所有与bytecode配套的生成器,在初始化模板表时作为gen参数传给相应的Template。
Template类的定义如下:
源代码位置:hotspot/src/share/vm/interpreter/templateTable.hpp // A Template describes the properties of a code template for a given bytecode // and provides a generator to generate the code template. class Template VALUE_OBJ_CLASS_SPEC { private: enum Flags { // 字节码指令指的是该字节码的操作数是否存在于字节码里面 uses_bcp_bit, // set if template needs the bcp pointing to bytecode does_dispatch_bit,// set if template dispatches on its own calls_vm_bit, // set if template calls the vm wide_bit // set if template belongs to a wide instruction }; typedef void (*generator)(int arg); int _flags; // describes interpreter template properties (bcp unknown) TosState _tos_in; // tos cache state before template execution TosState _tos_out; // tos cache state after template execution generator _gen; // template code generator int _arg; // argument for template code generator ... // Templates static Template* template_for(Bytecodes::Code code) { Bytecodes::check(code); return &_template_table[code]; } static Template* template_for_wide(Bytecodes::Code code) { Bytecodes::wide_check(code); return &_template_table_wide[code]; } };
调用的template_for()与template_for_wild()方法从_template_table或_template_for_wild数组中取值。这2个变量定义在TemplateTable类中,如下:
static Template _template_table [Bytecodes::number_of_codes]; static Template _template_table_wide[Bytecodes::number_of_codes];
继续看TemplateTable::def(0函数的各个参数,解释如下:
(1)_flags:是一个标志,低四位分别表示:
- uses_bcp_bit,标志需要使用字节码指针(byte code pointer,数值为字节码基址+字节码偏移量)
- does_dispatch_bit,标志是否在模板范围内进行转发,如跳转类指令会设置该位
- calls_vm_bit,标志是否需要调用JVM函数,在调用TemplateTable::call_VM()函数时都会判断是否有这个标志,通常方法调用JVM函数时都会通过调用TemplateTable::call_VM()函数来间接完成调用。JVM函数就是用C++写的函数。
- wide_bit,标志是否是wide指令(使用附加字节扩展全局变量索引)
(2)_tos_in:表示模板执行前的TosState(操作数栈栈顶元素的数据类型,TopOfStack,用来检查模板所声明的输出输入类型是否和该函数一致,以确保栈顶元素被正确使用)
(3)_tos_out:表示模板执行后的TosState
(4)_gen:表示模板生成器(函数指针),这个函数会为对应的字节码生成对应的执行逻辑
(5)_arg:为模板生成器传递的参数
再来看一下TemplateTable::initialize()方法中对def()函数的调用,以_iinc(将局部变量增加1)为例,调用如下:
def( Bytecodes::_iinc, // 字节码指令 ubcp|____|clvm|____, // 标志 vtos, // 模板执行前的TosState vtos, // 模板执行后的TosState iinc , // 模板生成器,是一个iinc()函数的指针 _ // 不需要模板生成器参数 );
设置标志位uses_bcp_bit和calls_vm_bit,表示iinc指令的生成器需要使用bcp指针函数at_bcp(),且需要调用JVM函数,下面给出了生成器的定义:
源代码位置:/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp void TemplateTable::iinc() { transition(vtos, vtos); __ load_signed_byte(rdx, at_bcp(2)); // get constant locals_index(rbx); __ addl(iaddress(rbx), rdx); }
iinc指令的格式如下:
iinc index const
操作码iinc占用一个字节,而index与const分别占用一个字节。使用at_bcp()函数获取iinc指令的操作数,2表示偏移2字节,所以会将const取出来存储到rdx中。调用locals_index()函数取出index,locals_index()就是JVM函数。最终生成的汇编如下:
// %r13存储的是指向字节码的指针,偏移2字节后取出const存储到%edx 0x00007fffe101a210: movsbl 0x2(%r13),%edx // 取出index存储到%ebx 0x00007fffe101a215: movzbl 0x1(%r13),%ebx 0x00007fffe101a21a: neg %rbx // %r14指向本地变量表的首地址,将%edx加到%r14+%rbx*8指向的内存所存储的值上 // 之所以要对%rbx执行neg进行符号反转,是因为在Linux内核的操作系统上,栈是向低地址方向生长的 0x00007fffe101a21d: add %edx,(%r14,%rbx,8)
不过这里并不会调用iinc()函数生成对应的汇编代码,只是将传递给def()函数的各种信息保存到Template对象中,在TemplateTable::def()方法中,通过template_for()或template_for_wild()方法获取到数组中对应的Template对象后,就会调用Template::initialize()方法,实现如下:
void Template::initialize(int flags, TosState tos_in, TosState tos_out, generator gen, int arg) { _flags = flags; _tos_in = tos_in; _tos_out = tos_out; _gen = gen; _arg = arg; }
可以看到,只是将信息保存到对应的Template对象中,这样就可以根据字节码索引从数组中获取对应的Template对象,进而获取相关信息。下一篇我们将会看到对这些信息的使用。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
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引用类型
50、CallStub栈帧
52、generate_fixed_frame()方法生成Java方法栈帧
54、虚拟机执行模式
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!