hotspot解释器和JIT
通过《编译原理》系列文章,我们可以创造出运行环境,然后根据程序的语义直接执行,也可能翻译成中间代码(机器码,汇编码)。这两种方式分别被称为解释执行和编译执行。
JVM中的编译器
在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)。
为何HotSpot虚拟器要解释器和编译器并存?
尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。
解释器的执行,抽象的看是这样的:
输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:
输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。JIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”
- 只被调用一次,例如类的构造器(class initializer,<clinit>())
- 没有循环
对只执行一次的代码做JIT编译再执行,可以说是得不偿失。对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。只有对频繁执行的代码,JIT编译才能保证有正面的收益。
对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。
- 解释模式:可通过-Xint指定,让JVM以解释模式运行Java程序
- 编译模式:可通过-Xcomp指定,让JVM以编译模式运行Java程序
- 混合模式(默认):可能通过-Xmixed指定,让JVM以解释+编译模式运行Java程序
解释器
在hotspot中,Interpreter是对外的一个解释器的包装类,通过宏定义的方式决定使用CppInterpreter(c++解释器)或者TemplateInterpreter(模板解释器)。其中模板解释器为默认解释器。下文相关介绍均以TemplateInterpreter及x86_64为前提介绍。
//AbstractInterpreter定义了基于汇编模型的解释器以及解释生成器的抽象行为
//hotspot/src/share/vm/interpreter/AbstractInterpreter.hpp
//hotspot/src/cpu/x86/vm/templateInterpreter_x86_64.cpp
class AbstractInterpreter: AllStatic {public://根据不同的方法特性,给方法定义不同的类型//为啥需要区分这么多种MethodKind了?主要是为了对特殊的MethodKind的方法做特殊处理,以获取最大的执行性能力enum MethodKind {zerolocals, // method needs locals initializationzerolocals_synchronized,// method needs locals initialization & is synchronizednative,// native methodnative_synchronized,// native method & is synchronizedempty,// empty method (code: _return)accessor,// accessor method (code: _aload_0, _getfield, _(a|i)return)abstract,// abstract method (throws an AbstractMethodException)//枚举在C/C++中其实就是一个int常量,默认情况下枚举值从0开始,依次往下递增,上述method_handle_invoke_LAST的定义比较特殊,正常method_handle_invoke_LAST应该比method_handle_invoke_FIRST大1,上述定义下就大vmIntrinsics::LAST_MH_SIG_POLY - vmIntrinsics::FIRST_MH_SIG_POLY,即method_handle_invoke_FIRST和method_handle_invoke_LAST之间实际还有几个未定义的但是合法的枚举值。method_handle_invoke_FIRST,method_handle_invoke_LAST = (method_handle_invoke_FIRST + (vmIntrinsics::LAST_MH_SIG_POLY- vmIntrinsics::FIRST_MH_SIG_POLY)),//....//枚举在C/C++中其实就是一个int常量,默认情况下枚举值从0开始,依次往下递增,相当于枚举长度number_of_method_entries};//_entry_table是AbstractInterpreter定义的一个protected address数组,数组长度是MethodKind的枚举值number_of_method_entries,即每个MethodKind都会有一个对应的表示方法执行入口地址的addressstatic address _entry_table[number_of_method_entries];//根据方法类型,从_entry_table中取出对应的方法处理static address entry_for_kind(MethodKind k){ //校验MethodKind是否合法,MethodKind是一个枚举,这里是检查枚举值assert(0 <= k && k < number_of_method_entries, "illegal kind"); return _entry_table[k]; }static address entry_for_method(methodHandle m) { return entry_for_kind(method_kind(m)); }//根据方法,确定方法类型AbstractInterpreter::MethodKind AbstractInterpreter::method_kind(methodHandle m) {//如果是抽象方法,直接返回abstractif (m->is_abstract()) return abstract;//如果是MethodHandle内部的私有方法if (m->is_method_handle_intrinsic()) {//vmIntrinsics::ID就是用来给部分特殊方法打标的vmIntrinsics::ID id = m->intrinsic_id();//校验ID是否合法,ID也是一个枚举,判断是否在枚举值范围内assert(MethodHandles::is_signature_polymorphic(id), "must match an intrinsic");//根据ID计算MethodKind枚举值MethodKind kind = (MethodKind)( method_handle_invoke_FIRST +((int)id - vmIntrinsics::FIRST_MH_SIG_POLY) );//校验计算的MethodKind枚举值是否合法 assert(kind <= method_handle_invoke_LAST, "parallel enum ranges");return kind;}//.....// Native method?// Note: This test must come _before_ the test for intrinsic// methods. See also comments below.//如果是本地方法if (m->is_native()) {//不能是MethodHandle内部的私有方法assert(!m->is_method_handle_intrinsic(), "overlapping bits here, watch out");//根据本地方式是否是同步和非同步方return m->is_synchronized() ? native_synchronized : native;}// 如果是同步方法if (m->is_synchronized()) {return zerolocals_synchronized;}//如果是构造方法,且要求在初始化时注册finalizer方法if (RegisterFinalizersAtInit && m->code_size() == 1 &&m->intrinsic_id() == vmIntrinsics::_Object_init) {// We need to execute the special return bytecode to check for// finalizer registration so create a normal frame.return zerolocals;}// 如果是空方法if (m->is_empty_method()) {return empty;}//如果是特殊的函数计算方法switch (m->intrinsic_id()) {case vmIntrinsics::_dsin : return java_lang_math_sin ;case vmIntrinsics::_dcos : return java_lang_math_cos ;case vmIntrinsics::_dtan : return java_lang_math_tan ;case vmIntrinsics::_dabs : return java_lang_math_abs ;case vmIntrinsics::_dsqrt : return java_lang_math_sqrt ;case vmIntrinsics::_dlog : return java_lang_math_log ;case vmIntrinsics::_dlog10: return java_lang_math_log10;case vmIntrinsics::_dpow : return java_lang_math_pow ;case vmIntrinsics::_dexp : return java_lang_math_exp ;case vmIntrinsics::_Reference_get:return java_lang_ref_reference_get;}//获取属性的方法,如this.a,a是属性,对应的字节码指令就是getfieldif (m->is_accessor()) {assert(m->size_of_parameters() == 1, "fast code for accessors assumes parameter size = 1");return accessor;}//其他的非本地非空非初始化的普通方法return zerolocals;
}}
//模板解释器生成器
//hotspot/src/cpu/x86/vm/TemplateInterpreterGenerator.cpp
class TemplateInterpreterGenerator{void TemplateInterpreterGenerator::generate_all() {//...#define method_entry(kind) \{ CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \}// all non-native method kindsmethod_entry(zerolocals)method_entry(zerolocals_synchronized)method_entry(empty)method_entry(accessor)method_entry(abstract)method_entry(java_lang_math_sin )method_entry(java_lang_math_cos )method_entry(java_lang_math_tan )method_entry(java_lang_math_abs )method_entry(java_lang_math_sqrt )method_entry(java_lang_math_log )method_entry(java_lang_math_log10)method_entry(java_lang_math_exp )method_entry(java_lang_math_pow )method_entry(java_lang_ref_reference_get)//...set_entry_points_for_all_bytes(); }//给所有的字节码生成对应的汇编指令 void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() {//逐一遍历所有的字节码for (int i = 0; i < DispatchTable::length; i++) {Bytecodes::Code code = (Bytecodes::Code)i;//如果定义了这个字节码if (Bytecodes::is_defined(code)) {//生成对应字节码的汇编指令set_entry_points(code);} else {//将其标记成未实现set_unimplemented(i);}}}void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {CodeletMark cm(_masm, Bytecodes::name(code), code);// 初始化 entry pointsassert(_unimplemented_bytecode != NULL, "should have been generated before");assert(_illegal_bytecode_sequence != NULL, "should have been generated before");address bep = _illegal_bytecode_sequence;address zep = _illegal_bytecode_sequence;address cep = _illegal_bytecode_sequence;address sep = _illegal_bytecode_sequence;address aep = _illegal_bytecode_sequence;address iep = _illegal_bytecode_sequence;address lep = _illegal_bytecode_sequence;address fep = _illegal_bytecode_sequence;address dep = _illegal_bytecode_sequence;address vep = _unimplemented_bytecode;address wep = _unimplemented_bytecode;//如果定义了字节码if (Bytecodes::is_defined(code)) {//获取该字节码对应的TemplateTemplate* t = TemplateTable::template_for(code);assert(t->is_valid(), "just checking");//生成汇编代码,最终调用Template::generate方法生成set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);}//如果是宽字节if (Bytecodes::wide_is_defined(code)) {//获取该字节码对应的TemplateTemplate* t = TemplateTable::template_for_wide(code);assert(t->is_valid(), "just checking");//生成汇编代码,最终调用Template::generate方法生成set_wide_entry_point(t, wep);}// 将生成的 entry points放入_normal_table中EntryPoint entry(bep, zep, cep, sep, aep, iep, lep, fep, dep, vep);Interpreter::_normal_table.set_entry(code, entry);Interpreter::_wentry_point[code] = wep;}void TemplateInterpreterGenerator::set_unimplemented(int i) {address e = _unimplemented_bytecode;EntryPoint entry(e, e, e, e, e, e, e, e, e, e);Interpreter::_normal_table.set_entry(i, entry);Interpreter::_wentry_point[i] = _unimplemented_bytecode;}//大部分MethodKind对应的address都是通过generate_method_entry方法生成//method_handle_invoke_FIRST到method_handle_invoke_LAST之间的几个,他们是通过AbstractInterpreterGenerator::initialize_method_handle_entries完成初始化的//hotspot/src/cpu/x86/vm/TemplateInterpreter_x86_64.cppaddress AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter::MethodKind kind) {// determine code generation flagsbool synchronized = false;address entry_point = NULL;InterpreterGenerator* ig_this = (InterpreterGenerator*)this;switch (kind) {case Interpreter::zerolocals : break;case Interpreter::zerolocals_synchronized: synchronized = true; break;case Interpreter::native : entry_point = ig_this->generate_native_entry(false); break;case Interpreter::native_synchronized : entry_point = ig_this->generate_native_entry(true); break;case Interpreter::empty : entry_point = ig_this->generate_empty_entry(); break;case Interpreter::accessor : entry_point = ig_this->generate_accessor_entry(); break;case Interpreter::abstract : entry_point = ig_this->generate_abstract_entry(); break;//....default:fatal(err_msg("unexpected method kind: %d", kind));break;}if (entry_point) {return entry_point;}return ig_this->generate_normal_entry(synchronized);}
}
//TosState枚举用来表示字节码指令执行前后栈顶的值的类型,栈顶的值可能保存在一个或者多个CPU寄存器中,需要通过值类型正确的读取值,将栈顶的值保存到一个或者多个寄存器中的技术就称为栈顶缓存技术,默认情况下栈顶的值保存在rax寄存器中。如果TosState为vtos则表示未使用栈顶缓存。
//hotspot/src/share/vm/utilities/globalDefinitions.hpp
enum TosState { // describes the tos cache contentsbtos = 0, // byte, bool tos cachedztos = 1, // byte, bool tos cachedctos = 2, // char tos cachedstos = 3, // short tos cacheditos = 4, // int tos cachedltos = 5, // long tos cachedftos = 6, // float tos cacheddtos = 7, // double tos cachedatos = 8, // object cachedvtos = 9, // tos not cachednumber_of_states,ilgl // illegal state: should not occur
};//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//栈顶缓存技术,TosState不同,所保存的地方可能不一样
//用一个数组把地址做保存下来,然后运行时根据取不同的值
class EntryPoint VALUE_OBJ_CLASS_SPEC {private:address _entry[number_of_states];public:// ConstructionEntryPoint();EntryPoint(address bentry, address zentry, address centry, address sentry, address aentry, address ientry, address lentry, address fentry, address dentry, address ventry);address entry(TosState state) const;// return target address for a given tosca statevoid set_entry(TosState state, address entry);// set target address for a given tosca state
};//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//EntryPoint的数组形式
class DispatchTable VALUE_OBJ_CLASS_SPEC {public:enum { length = 1 << BitsPerByte };//默认为8private:address _table[number_of_states][length];// dispatch tables, indexed by tosca and bytecodepublic:// AttributesEntryPoint entry(int i) const; // return entry point for a given bytecode ivoid set_entry(int i, EntryPoint& entry); // set entry point for a given bytecode iaddress* table_for(TosState state) { return _table[state]; }address* table_for() { return table_for((TosState)0); }int distance_from(address *table) { return table - table_for(); }int distance_from(TosState state) { return distance_from(table_for(state)); }
};//用来描述一个字节码模板的属性,并提供一个用来生成字节码模板的函数
//根据不同的指令配置不同的参数,生成不同的汇编代码
//hotspot/src/share/vm/interpreter/templateTable.hpp
class Template VALUE_OBJ_CLASS_SPEC {typedef void (*generator)(int arg); //用来描述字节码模板的属性,相关属性通过枚举Flags指定 int _flags;//执行字节码指令前的栈顶值类型 TosState _tos_in;//执行字节码指令后的栈顶值类型TosState _tos_out; //generator是用来生成字节码模板的函数的别名 generator _gen;//用来生成字节码模板的参数 int _arg;
};//Template的管理工具
//hotspot/src/share/vm/interpreter/templateTable.hpp
class TemplateTable: AllStatic {//是否完成初始化 static bool _is_initialized; //表示各字节码指令对应的Templatestatic Template _template_table [Bytecodes::number_of_codes];//宽字节下的各字节码指令对应的Templatestatic Template _template_table_wide[Bytecodes::number_of_codes];//当前正在生成的Template模板static Template* _desc;//用来生成字节码模板的Assembler实例static InterpreterMacroAssembler* _masm;
}
//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//AbstractInterpreter基础上增加了很多的完成特定功能的函数的调用入口
class TemplateInterpreter: public AbstractInterpreter {// the active dispatch table (used by the interpreter for dispatch)static DispatchTable _active_table; // the normal dispatch table (used to set the active table in normal mode) static DispatchTable _normal_table; void TemplateInterpreter::initialize() {if (_code != NULL) return;//校验字节码的个数必须小于等于DispatchTable的长度assert((int)Bytecodes::number_of_codes <= (int)DispatchTable::length,"dispatch table too small");//初始化一些统计数据AbstractInterpreter::initialize();//把所用指令创建Template,并放入数组中TemplateTable::initialize();// generate interpreter{ ResourceMark rm;TraceTime timer("Interpreter generation", TraceStartupTime);//InterpreterCodeSize是在平台相关的templateInterpreter_x86.hpp中定义的,64位下是256 * 1024int code_size = InterpreterCodeSize;NOT_PRODUCT(code_size *= 4;) // debug uses extra interpreter code space//创建一个StubQueue_code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,"Interpreter");//初始化InterpreterGenerator,初始化的时候会生成所有的调用函数InterpreterGenerator g(_code);if (PrintInterpreter) print();}// 将_normal_table标记为激活的_active_table = _normal_table;}}
//InterpreterCodelet表示一段解释器代码,所有的解释器代码都放在InterpreterCodelet中,同时还包含了额外的用于打印和调试的信息
//hotspot/src/share/vm/interpreter/interpreter.hpp
// ________
// stub -->| | <--+
// | data | |
// |________| |
// code_begin -->| | |
// | | |
// | code | | size
// | | |
// |________| |
// code_end -->| | |
// | data | |
// |________| |
// <--+
class InterpreterCodelet: public Stub {//内存大小int _size; //当前InterpreterCodelet的描述字符串const char* _description;//具体字节码指令的枚举Bytecodes::Code _bytecode;
}//StubQueue表示一个用来保存Stub的队列
class StubQueue: public CHeapObj<mtCode> {friend class VMStructs;private://InterpreterCodelet的代理工具类//_stub_interface+_stub_buffer,得出InterpreterCodeletStubInterface* _stub_interface; address _stub_buffer; //存储Stub的buffer的内存大小int _buffer_size;//存储Stub的buffer的limit的位置,能够分配Stub的上限int _buffer_limit;//队列中第一个Stub相对于_stub_buffer的偏移量int _queue_begin;//队列后第一个Stub相对于_stub_buffer的偏移量,即下一个可分配Stub的地址int _queue_end; //队列中保存的stub的个数int _number_of_stubs;
}//CodeletMark实例化的时候会向StubQueue分配stub
//创建宏汇编解释器masm,这样子通过masm生成的代码就在stub里面了
class CodeletMark(InterpreterMacroAssembler*& masm,const char* description,Bytecodes::Code bytecode = Bytecodes::_illegal)://code()方法返回的就是AbstractInterpreter的StubQueue实例,即通过StubQueue请求指定大小的Stub_clet((InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size())),//通过_clet的两个属性,调用CodeBuffer(address code_start, csize_t code_size)方法初始化CodeBuffer_cb(_clet->code_begin(), _clet->code_size()){ //校验_clet非空,即Stub分配成功assert (_clet != NULL, "we checked not enough space already");// 初始化InterpreterCodelet_clet->initialize(description, bytecode);// 创建一个新的InterpreterMacroAssembler实例,new返回的是masm指针,&masm获取就是InterpreterMacroAssembler的指针的指针masm = new InterpreterMacroAssembler(&_cb);_masm = &masm;}~CodeletMark() {//填充对齐,避免打印时末尾显示乱码(*_masm)->align(wordSize);//刷新指令缓存(*_masm)->flush();// 提交Stub,因为request是已经获取全局锁了,所以不存起其他线程同时修改StubQueue实例AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size(), (*_masm)->code()->strings());//确保没有其他人可以在CodeletMark的生命周期外使用_masm,然后InterpreterMacroAssembler会通过ResoureMark自动销毁掉*_masm = NULL;}
解释如何工作
《JVM方法调用》中提到的entry_point具体怎么生成的?
Threads::create_vm()
init_globals()
interpreter_init()()
emplateInterpreter::initialize()
TemplateInterpreterGenerator() // 构造函数
TemplateInterpreterGenerator::generate_all()
- 在方法调用的时候,根据当前方法求出方法类型MethodKind
AbstractInterpreter::method_kind(methodHandle m)
- 根据方法的MethodKind,在_entry_table查找到对应的地址
address entry_for_kind(MethodKind k)
- 根据地址entry_point进行调用,普通方法对应 generate_normal_entry
方法中各指令是如何执行的
- 为各指令创建模板Template,并被TemplateTable管理
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize()
TemplateTable::initialize
得template
def(Bytecodes::iload_1 , |||, vtos, itos,iload , 1 );
def(Bytecodes::iconst_1 , |||, vtos, itos, iconst , 1 );
def(Bytecodes::iadd , |||___, itos, itos, iop2 , add );
- 为所有template生成汇编码,并存放StubQueue
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize()
TemplateInterpreterGenerator::generate_all()
TemplateInterpreterGenerator::set_entry_points_for_all_bytes
TemplateInterpreterGenerator::set_entry_points
通过CodeletMark向StubQueue申请空间,也创建出_masm汇编器
CodeletMark cm(_masm, Bytecodes::name(code), code);
最终调用Template::generate方法生成汇编代码
iload_1 27 iload_1 [0x00000001122d4600, 0x00000001122d4660] 96 bytes
iconst_1 4 iconst_1 [0x00000001122d3740, 0x00000001122d37a0] 96 bytes
iadd 96 iadd [0x00000001122d6720, 0x00000001122d6760] 64 bytes
汇编代码的地址入到转发表DispatchTable中
Interpreter::_normal_table.set_entry(code, entry);
3.在《JVM方法调用》方法调用的时候调用
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {// load next bytecode (load before advancing r13 to prevent AGI)//r13保存的就是方法的字节码的内存位置,step表示跳过的字节数,第一次调用时没有传step,step默认为0//读取第一个字节码到rbx中load_unsigned_byte(rbx, Address(r13, step));//将r13中的地址自增stepincrement(r13, step);dispatch_base(state, Interpreter::dispatch_table(state));
}void InterpreterMacroAssembler::dispatch_base(TosState state,address* table,bool verifyoop) {//64位下是空实现 verify_FPU(1, state);//校验栈帧sizeif (VerifyActivationFrameSize) {Label L;mov(rcx, rbp);subptr(rcx, rsp);int32_t min_frame_size =(frame::link_offset - frame::interpreter_frame_initial_sp_offset) *wordSize;cmpptr(rcx, (int32_t)min_frame_size);jcc(Assembler::greaterEqual, L);stop("broken stack frame");bind(L);}//如果栈顶缓存是对象,则校验对象if (verifyoop) {verify_oop(rax, state);}//将table的地址拷贝到rscratch1寄存器中,这时的table地址实际上是DispatchTable的二维数组_table属性的第一维数组的地址lea(rscratch1, ExternalAddress((address)table));//此时rbx是待执行的字节码,即跳转到对应字节码的汇编指令上,DispatchTable的二维数组_table属性的第二维就是字节码,即在rscratch1地址上偏移rbx*8个字节就可以找到对应的字节码指令实现了jmp(Address(rscratch1, rbx, Address::times_8));
}
JIT
即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。
client模式:可通过-client指定,使用c1编译器,c1对编译进行快速的优化
sever模式:可通过-sever指定,使用c2编译器,c2对编译进行更多优化,编译比c1耗时,但产生的机器代码比c1更高效
HotSpot虚拟机中常见的即时编译器c1,c2.c1能做一些快速的优化,c2优化慢,但产生更高效的代码。因此 jdk6开始加入了多级编译器的概念,解释器,c1,c2编译器一起协同运行。
- CompLevel_none,采用解释器执行
- CompLevel_simple,采用c1编翻译器
- CompLevel_limited_profile,采用c1编翻译器,采集[方法的方法调用计数,循环调用计数]
- CompLevel_full_profile,采用c1编翻译器,采集[方法的方法调用计数,循环调用计数,profile信息]
- CompLevel_optimization,采用c2编翻译器
其中0,2,3三个级别下都会周期性的通知 AdvancedThresholdPolicy某个方法的方法调用计数即invocation counters and循环调用计数即backedge counters,不同级别下通知的频率不同。这些通知用来决定如何调整编译级别。
某个方法刚开始执行时是通过解释器解释执行的,即level 0,然后AdvancedThresholdPolicy会综合如下两个因素和调用计数将编译级别调整为2或者3:
C2编译任务队列的长度决定了下一个编译级别。
据观察,level 2级别下的编译比level3级别下的编译快30%,因此我们应该在只有已经收集了充分的profile信息后才采用level 3编译,从而尽可能缩短level3级别下编译的耗时。
- 当C2的编译任务队列太长的时候,如果选择level3则编译会被卡住直到C2将之前的编译任务处理完成,这时如果选择Level2编译则很快完成编译。
- 当C2的编译压力逐步减小,就可以重新在level 3下编译并且开始收集profile信息。
根据C1编译任务队列的长度用来动态的调整阈值.在编译器过载时,会将已经编译完成但是不在使用的方法从编译队列中移除。
当level 3下profile信息收集完成后就会转换成level 4了,可根据C2编译任务队列的长度来动态调整转换的阈值。(l3->l4)
当经过C1编译完成后,基于方法的代码块的数量,循环的数量等信息可以判断一个方法是否是琐碎(trivial)的,这类方法在C2编译下会产生和C1编译一样的代码,因此这时会用level1的编译代替level 4的编译。
当C1和C2的编译任务队列的长度足够短,也可在level 0下开启profile信息收集。编译队列通过优先级队列实现,每个添加到编译任务队列的方法都会周期的计算其在单位时间内增加的调用次数,每次从编译队列中获取任务时,都选择单位时间内调用次数最大的一个。基于此,我们也可以将那些编译完成后不再使用,调用次数不再增加的方法从编译任务队列中移除。
常见的各级别转换路径如下:
- 0 -> 3 -> 4,最常见的转换路径,需要注意这种情况下profile可以从level 0开始,level3结束。
- 0 -> 2 -> 3 -> 4,当C2的编译压力较大会发生这种情形,本来应该从0直接转换成3,为了减少编译耗时就从0转换成2,等C2的编译压力降低再转换成3
- 0 -> (3->2) -> 4,这种情形下已经把方法加入到3的编译队列中了,但是C1队列太长了导致在0的时候就开启了profile,然后就调整了编译策略按level 2来编译,即时还是3的队列中,这样编译更快
- 0 -> 3 -> 1 or 0 -> 2 ->1,当一个方法被C1编译后,被标记成trivial的,因为这类方法C2编译的代码和C1编译的代码是一样的,所以不使用4,改成1
- 0 ->4,一个方法C1编译失败且一直在收集profile信息,就从0切换到4
//java虚拟机参数解析类
//hotspot/src/share/vm/runtime/arguments.cpp
class Arguments {void Arguments::set_mode_flags(Mode mode) {//设置编译参数默认值set_java_compiler(false);_mode = mode;//....UseInterpreter = true;UseCompiler = true;UseLoopCounter = true;switch (mode) {default:ShouldNotReachHere();break;//解释模式:可通过-Xint指定 case _int:UseCompiler = false;UseLoopCounter = false;AlwaysCompileLoopMethods = false;UseOnStackReplacement = false;break;//混合模式,默认格式 case _mixed:/// same as defaultbreak;//编译模式:可通过-Xcomp指定 case _comp:UseInterpreter = false;BackgroundCompilation = false;ClipInlining = false;if (TieredCompilation) {//多级编译器Tier3InvokeNotifyFreqLog = 0;Tier4InvocationThreshold = 0;}break;}
}//表示一个调用计数器,可以在初始化时为不同状态下的InvocationCounter定义对应的阈值和触发的动作,当计数超过阈值时自动执行初始化时指定的触发动作
//hotspot/src/share/vm/interpreter/invocationCounter.hpp
class InvocationCounter{//为了节省内存空间,InvocationCounter的状态和计数被编码在一个int数据(4字节,32位,对应_counter属性)// bit no: |31 3| 2 | 1 0 |// format: [count|carry|state]//count具体的计数器//carry相当于一个分隔符//state//wait_for_nothing, do nothing when count() > limit()//wait_for_compile,introduce nmethod when count() > limit()unsigned int _counter; //执行方法编译的阈值static int InterpreterInvocationLimit;//执行栈上替换的阈值 static int InterpreterBackwardBranchLimit;//收集解释器执行性能数据的阈值 static int InterpreterProfileLimit; //不同State下的阈值,到达对应数值,状态开始变化static int _init [number_of_states]; //不同State下的达到阈值,状态开始变化,执行的动作 static Action _action[number_of_states]; void InvocationCounter::init() {_counter = 0; //所有的位都初始化成0reset();}void InvocationCounter::reset() {set_state(wait_for_compile);}void InvocationCounter::set_state(State state) {//校验state的合法性assert(0 <= state && state < number_of_states, "illegal state");//获取该state下的调用计数,初始为0int init = _init[state];// prevent from going to zero, to distinguish from never-executed methods//初始状态下count()返回0,init也是0//当运行一段时间发生状态切换后,count()返回值大于0,如果此时init==0说明是第一次执行此状态下的调用,将init初始化为1if (init == 0 && count() > 0) init = 1;int carry = (_counter & carry_mask);//初始化counter_counter = (init << number_of_noncount_bits) | carry | state;}//返回总的调用计数int count() const { return _counter >> number_of_noncount_bits; } void InvocationCounter::reinitialize(bool delay_overflow) {//确保number_of_states小于等于4guarantee((int)number_of_states <= (int)state_limit, "adjust number_of_state_bits");//设置两种状态下的触发动作def(wait_for_nothing, 0, do_nothing);//如果延迟处理,delay_overflow肯定是true,所以不会走到dummy_invocation_counter_overflow,该方法是空实现if (delay_overflow) {def(wait_for_compile, 0, do_decay);} else {def(wait_for_compile, 0, dummy_invocation_counter_overflow);}//计算InterpreterInvocationLimit等阈值InterpreterInvocationLimit = CompileThreshold << number_of_noncount_bits;InterpreterProfileLimit = ((CompileThreshold * InterpreterProfilePercentage) / 100)<< number_of_noncount_bits;if (ProfileInterpreter) {InterpreterBackwardBranchLimit = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)) / 100;} else {InterpreterBackwardBranchLimit = ((CompileThreshold * OnStackReplacePercentage) / 100) << number_of_noncount_bits;}//校验计算结果的合法性assert(0 <= InterpreterBackwardBranchLimit,"OSR threshold should be non-negative");assert(0 <= InterpreterProfileLimit &&InterpreterProfileLimit <= InterpreterInvocationLimit,"profile threshold should be less than the compilation threshold ""and non-negative");}
}// Method中的一个计数属性
class MethodCounters: public MetaspaceObj {//方法调用计数器 InvocationCounter _invocation_counter; //循环计数器InvocationCounter _backedge_counter;
}
//参数是generate_normal_entry三个地址
void InterpreterGenerator::generate_counter_incr(Label* overflow,//方法调用次数到达,跳去提交编译Label* profile_method,//以集profile数据的地址Label* profile_method_continue//以集profile后的地址) {Label done;//如果启用分级编译,server模式下默认启用if (TieredCompilation) {//因为InvocationCounter的_counter中调用计数部分是前29位,所以增加一次调用计数不是从1开始,而是1<<3即8int increment = InvocationCounter::count_increment;//Tier0InvokeNotifyFreqLog默认值是7,count_shift是_counter属性中非调用计数部分的位数,这里是3int mask = ((1 << Tier0InvokeNotifyFreqLog) - 1) << InvocationCounter::count_shift;Label no_mdo;//如果开启性能收集if (ProfileInterpreter) {//校验Method中的_method_data属性非空,如果为空则跳转到no_mdo__ movptr(rax, Address(rbx, Method::method_data_offset()));__ testptr(rax, rax);__ jccb(Assembler::zero, no_mdo);//获取MethodData的_invocation_counter属性的_counter属性的地址const Address mdo_invocation_counter(rax, in_bytes(MethodData::invocation_counter_offset()) +in_bytes(InvocationCounter::counter_offset()));//此时rcx中的值无意义 __ increment_mask_and_jump(mdo_invocation_counter, increment, mask, rcx, false, Assembler::zero, overflow);__ jmp(done);}__ bind(no_mdo);//获取MethodCounters的_invocation_counter属性的_counter属性的地址,get_method_counters方法会将MethodCounters的地址放入rax中const Address invocation_counter(rax,MethodCounters::invocation_counter_offset() +InvocationCounter::counter_offset());//获取MethodCounters的地址并将其放入rax中 __ get_method_counters(rbx, rax, done);//增加计数__ increment_mask_and_jump(invocation_counter, increment, mask, rcx,false, Assembler::zero, overflow);__ bind(done);} else {//获取MethodCounters的_backedge_counter属性的_counter属性的地址const Address backedge_counter(rax,MethodCounters::backedge_counter_offset() +InvocationCounter::counter_offset());//获取MethodCounters的_invocation_counter属性的_counter属性的地址const Address invocation_counter(rax,MethodCounters::invocation_counter_offset() +InvocationCounter::counter_offset());//获取MethodCounters的地址并将其放入rax中__ get_method_counters(rbx, rax, done);//如果开启性能收集if (ProfileInterpreter) {//因为value为0,所以这里啥都不做__ incrementl(Address(rax,MethodCounters::interpreter_invocation_counter_offset()));}//更新invocation_counter__ movl(rcx, invocation_counter);__ incrementl(rcx, InvocationCounter::count_increment);__ movl(invocation_counter, rcx); // save invocation count__ movl(rax, backedge_counter); // load backedge counter//计算出status的位__ andl(rax, InvocationCounter::count_mask_value); // mask out the status bits//将rcx中的调用计数同rax中的status做且运算__ addl(rcx, rax); // add both counters// profile_method is non-null only for interpreted method so// profile_method != NULL == !native_callif (ProfileInterpreter && profile_method != NULL) {//如果rcx的值小于InterpreterProfileLimit,则跳转到profile_method_continue__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreterProfileLimit));__ jcc(Assembler::less, *profile_method_continue);//如果大于,则校验methodData是否存在,如果不存在则跳转到profile_method__ test_method_data_pointer(rax, *profile_method);}//比较rcx的值是否超过InterpreterInvocationLimit,如果大于等于则跳转到overflow__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreteoverflowrInvocationLimit));__ jcc(Assembler::aboveEqual, *overflow);__ bind(done);}
}void InterpreterMacroAssembler::increment_mask_and_jump(Address counter_addr,int increment, int mask,Register scratch, bool preloaded,Condition cond, Label* where) {//preloaded一般传false if (!preloaded) {//将_counter属性的值复制到scratch,即rcx中movl(scratch, counter_addr);}//将_counter属性增加incrementincrementl(scratch, increment);//将scratch寄存器中的值写入到_counter属性movl(counter_addr, scratch);//将mask与scratch中的值做且运算andl(scratch, mask);if (where != NULL) {//如果且运算的结果是0,即达到阈值的时候,则跳转到where,即overflow处jcc(cond, *where);}
}void InterpreterMacroAssembler::get_method_counters(Register method,Register mcs, Label& skip) {Label has_counters;//获取当前Method的_method_counters属性movptr(mcs, Address(method, Method::method_counters_offset()));//校验_method_counters属性是否非空,如果不为空则跳转到has_counterstestptr(mcs, mcs);jcc(Assembler::notZero, has_counters);//如果为空,则调用build_method_counters方法创建一个新的MethodCounterscall_VM(noreg, CAST_FROM_FN_PTR(address,InterpreterRuntime::build_method_counters), method);//将新的MethodCounters的地址放入mcs中,校验其是否为空,如果为空则跳转到skipmovptr(mcs, Address(method,Method::method_counters_offset()));testptr(mcs, mcs);jcc(Assembler::zero, skip); // No MethodCounters allocated, OutOfMemorybind(has_counters);
}void InterpreterGenerator::generate_counter_overflow(Label* do_continue) {// Asm interpreter on entry// r14 - locals// r13 - bcp// rbx - method// edx - cpool --- DOES NOT APPEAR TO BE TRUE// rbp - interpreter frame// On return (i.e. jump to entry_point) [ back to invocation of interpreter ]// Everything as it was on entry// rdx is not restored. Doesn't appear to really be set.//InterpreterRuntime::frequency_counter_overflow需要两个参数,第一个参数thread在执行call_VM时传递,第二个参数表明//调用计数超过阈值是否发生在循环分支上,如果否则传递NULL,我们传递0,即NULL,如果是则传该循环的跳转分支地址//这个方法返回编译后的方法的入口地址,如果编译没有完成则返回NULL__ movl(c_rarg1, 0);__ call_VM(noreg,CAST_FROM_FN_PTR(address,InterpreterRuntime::frequency_counter_overflow),c_rarg1);//恢复rbx中的Method*,method_offset是全局变量__ movptr(rbx, Address(rbp, method_offset)); // restore Method*//跳转到do_continue标签__ jmp(*do_continue, relocInfo::none);
}nmethod* InterpreterRuntime::frequency_counter_overflow(JavaThread* thread, address branch_bcp) {//非OSR,即非栈上替换方法,永远返回null,即不会立即执行编译,而是提交任务给后台编译线程编译nmethod* nm = frequency_counter_overflow_inner(thread, branch_bcp);assert(branch_bcp != NULL || nm == NULL, "always returns null for non OSR requests");if (branch_bcp != NULL && nm != NULL) {//目标方法是一个需要栈上替换的方法,因为frequency_counter_overflow_inner返回的nm没有加载,所以需要再次查找frame fr = thread->last_frame();Method* method = fr.interpreter_frame_method();int bci = method->bci_from(fr.interpreter_frame_bcp());nm = method->lookup_osr_nmethod_for(bci, CompLevel_none, false);}return nm;
}//branch_bcp表示调用计数超过阈值时循环跳转的地址
IRT_ENTRY(nmethod*,InterpreterRuntime::frequency_counter_overflow_inner(JavaThread* thread, address branch_bcp))// use UnlockFlagSaver to clear and restore the _do_not_unlock_if_synchronized// flag, in case this method triggers classloading which will call into Java.UnlockFlagSaver fs(thread);frame fr = thread->last_frame();//验证当前方法是解释执行方法assert(fr.is_interpreted_frame(), "must come from interpreter");//获取当前解释执行的方法methodHandle method(thread, fr.interpreter_frame_method());//branch_bcp非空则获取其相对于方法字节码起始地址code_base的偏移,否则等于InvocationEntryBci,InvocationEntryBci表明这是非栈上替换的方法编译const int branch_bci = branch_bcp != NULL ? method->bci_from(branch_bcp) : InvocationEntryBci;const int bci = branch_bcp != NULL ? method->bci_from(fr.interpreter_frame_bcp()) : InvocationEntryBci;//校验是否发生异常assert(!HAS_PENDING_EXCEPTION, "Should not have any exceptions pending");//如果要求栈上替换则返回该方法对应的nmethod,否则返回空,然后提交一个方法编译的任务给后台编译线程nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, NULL, thread);assert(!HAS_PENDING_EXCEPTION, "Event handler should not throw any exceptions");if (osr_nm != NULL) {//如果使用偏向锁,则将当前栈帧持有的所有偏向锁都释放调用,因为这些偏向锁在栈上替换的时候需要迁移if (UseBiasedLocking) {ResourceMark rm;GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();for( BasicObjectLock *kptr = fr.interpreter_frame_monitor_end();kptr < fr.interpreter_frame_monitor_begin();kptr = fr.next_monitor_in_interpreter_frame(kptr) ) {if( kptr->obj() != NULL ) {objects_to_revoke->append(Handle(THREAD, kptr->obj()));}}BiasedLocking::revoke(objects_to_revoke);}}return osr_nm;
IRT_ENDint Method::bci_from(address bcp) const {return bcp - code_base();
}nmethod* lookup_osr_nmethod_for(int bci, int level, bool match_level) {//method_holder方法返回该方法所属的Klassreturn method_holder()->lookup_osr_nmethod(this, bci, level, match_level);}nmethod* InstanceKlass::lookup_osr_nmethod(const Method* m, int bci, int comp_level, bool match_level) const {// This is a short non-blocking critical region, so the no safepoint check is ok.//获取操作OsrList的锁OsrList_lock->lock_without_safepoint_check();//返回_osr_nmethods_head属性,即栈上替换的nmethod链表的头nmethod* osr = osr_nmethods_head();nmethod* best = NULL;while (osr != NULL) {//校验这个方法是栈上替换方法assert(osr->is_osr_method(), "wrong kind of nmethod found in chain");if (osr->method() == m &&(bci == InvocationEntryBci || osr->osr_entry_bci() == bci)) {//如果要求comp_level匹配if (match_level) {//校验osr的comp_level与待查找方法的comp_level是否匹配if (osr->comp_level() == comp_level) {// Found a match - return it.OsrList_lock->unlock();return osr;}} else {//查找该方法编译优化级别最高的osr,如果找到了则返回if (best == NULL || (osr->comp_level() > best->comp_level())) {if (osr->comp_level() == CompLevel_highest_tier) {// Found the best possible - return it.OsrList_lock->unlock();return osr;}best = osr;}}}//不是目标方法,继续查找下一个osr = osr->osr_link();}OsrList_lock->unlock();//如果没有最高优化级别的osr,则要求其优化级别大于或者等于要求的级别if (best != NULL && best->comp_level() >= comp_level && match_level == false) {return best;}return NULL;
}
//编译好的字节会调用方法进行安装
void Method::set_code(methodHandle mh, nmethod *code) {//获取锁Patching_lockMutexLockerEx pl(Patching_lock, Mutex::_no_safepoint_check_flag);//校验code非空assert( code, "use clear_code to remove code" );//校验method原来的code为空或者不是栈上替换的nmethodassert( mh->check_code(), "" );//校验adaper不为空guarantee(mh->adapter() != NULL, "Adapter blob must already exist!");//下面的属性修改必须按照顺序依次执行//将目标方法的_code属性置为codemh->_code = code; // Assign before allowing compiled code to exec//获取编译级别int comp_level = code->comp_level();//如果大于该方法曾经的最高编译级别则更新if (comp_level > mh->highest_comp_level()) {mh->set_highest_comp_level(comp_level);}//让上述修改立即生效,内存屏障相关OrderAccess::storestore();
#ifdef SHARK//如果采用Shark编译器,则直接将方法调用入口地址改写成insts_beginmh->_from_interpreted_entry = code->insts_begin();
#else //如果采用非Shark即使用C1或者C2编译器//将_from_compiled_entry赋值成verified_entry_pointmh->_from_compiled_entry = code->verified_entry_point();OrderAccess::storestore();//如果不是MethodHandle的invoke等方法,即编译代码可以立即执行if (!mh->is_method_handle_intrinsic())//get_i2c_entry方法实际返回的是一个适配器,在_from_interpreted_entry和_from_compiled_entry之间的适配器mh->_from_interpreted_entry = mh->get_i2c_entry();
#endif //!SHARK
}address Method::get_i2c_entry() {assert(_adapter != NULL, "must have");return _adapter->get_i2c_entry();
}
//表示编译策略,即决定那个方法应该编译和用什么类型的编译器编译
//hotspot/src/share/vm/runtime/compilationPolicy.hpp
class CompilationPolicy : public CHeapObj<mtCompiler> {//单例,运行时使用的CompilationPolicystatic CompilationPolicy* _policy; //用于统计编译耗时static elapsedTimer _accumulated_time;//是否正在VM启动中static bool _in_vm_startup;//静态方法都是工具类方法,判断某个方法能否编译//must_be_compiled,can_be_compiled,can_be_osr_compiled//禁用某个方法的编译//disable_compilation //用来从CompileQueue队列中选取一个编译任务CompileTask//select_task//COMPILER2表示启用C2编译器,TIERED表示启用分级编译,这两个都是x86_64下默认添加的宏//server模式下默认开启分层编译是选择AdvancedThresholdPolicy编译策略。void compilationPolicy_init() {CompilationPolicy::set_in_vm_startup(DelayCompilationDuringStartup);switch(CompilationPolicyChoice) {case 0:CompilationPolicy::set_policy(new SimpleCompPolicy());break;case 1:#ifdef COMPILER2CompilationPolicy::set_policy(new StackWalkCompPolicy());#elseUnimplemented();#endifbreak;case 2:#ifdef TIEREDCompilationPolicy::set_policy(new SimpleThresholdPolicy());#elseUnimplemented();#endifbreak;case 3:#ifdef TIEREDCompilationPolicy::set_policy(new AdvancedThresholdPolicy());#elseUnimplemented();#endifbreak;default:fatal("CompilationPolicyChoice must be in the range: [0-3]");}CompilationPolicy::policy()->initialize();}
}
//简单的策略
//hotspot/src/share/vm/runtime/simpleThresholdPolicy.hpp
class SimpleThresholdPolicy : public CompilationPolicy {//表示C1和C2编译线程的数量int _c1_count, _c2_count;//event方法用于触发热点方法的编译,如果是osr方法则返回包含编译代码的nmethod实例,如果是非ors方法,则返回NULLnmethod* SimpleThresholdPolicy::event(methodHandle method, methodHandle inlinee,int branch_bci, int bci, CompLevel comp_level, nmethod* nm, JavaThread* thread) {//如果目标线程是只使用解释模式执行且编译级别就是解释执行,则返回NULLif (comp_level == CompLevel_none &&JvmtiExport::can_post_interpreter_events() &&thread->is_interp_only_mode()) {return NULL;}//...//如果是非栈上替换方法if (bci == InvocationEntryBci) {//提交方法编译任务method_invocation_event(method, inlinee, comp_level, nm, thread);} else {//如果是栈上替换方法,触发方法编译method_back_branch_event(method, inlinee, bci, comp_level, nm, thread);//查找不低于目标编译级别的nmethod实例nmethod* osr_nm = inlinee->lookup_osr_nmethod_for(bci, comp_level, false);if (osr_nm != NULL && osr_nm->comp_level() > comp_level) {// Perform OSR with new nmethodreturn osr_nm;}}return NULL;}void SimpleThresholdPolicy::compile(methodHandle mh, int bci, CompLevel level, JavaThread* thread) {//校验编译级别有效性assert(level <= TieredStopAtLevel, "Invalid compilation level");if (level == CompLevel_none) {return;}//如果不能通过C1编译则继续收集解释执行的性能信息,然后通过C2编译,如果不能通过C2编译,则只通过C1编译if (!can_be_compiled(mh, level)) {if (level == CompLevel_full_optimization && can_be_compiled(mh, CompLevel_simple)) {compile(mh, bci, CompLevel_simple, thread);}return;}//如果是栈上替换,但是无法编译则返回if (bci != InvocationEntryBci && mh->is_not_osr_compilable(level)) {return;}//如果该方法不再编译队列中if (!CompileBroker::compilation_is_in_queue(mh)) {if (PrintTieredEvents) {//打印日志print_event(COMPILE, mh, mh, bci, level);}//提交编译任务submit_compile(mh, bci, level, thread);}}}//分层策略
//hotspot/src/share/vm/runtime/advancedThresholdPolicy.hpp
class AdvancedThresholdPolicy : public SimpleThresholdPolicy {jlong _start_time;//启动时间double _increase_threshold_at_ratio;//表示当CodeCache已使用了指定比例时提升C1编译的阈值//方法调用void AdvancedThresholdPolicy::method_invocation_event(methodHandle mh, methodHandle imh,CompLevel level, nmethod* nm, JavaThread* thread) {//如果需要开启profile,则初始化方法的mdoif (should_create_mdo(mh(), level)) {create_mdo(mh, thread);}//如果开启方法编译,且该方法不再编译队列中if (is_compilation_enabled() && !CompileBroker::compilation_is_in_queue(mh)) {CompLevel next_level = call_event(mh(), level);if (next_level != level) {//执行方法编译compile(mh, InvocationEntryBci, next_level, thread);}}}//循环体编译,栈上替换void AdvancedThresholdPolicy::method_back_branch_event(methodHandle mh, methodHandle imh,int bci, CompLevel level, nmethod* nm, JavaThread* thread) {//..//执行方法编译compile(imh, bci, next_osr_level, thread);}//提交编译void AdvancedThresholdPolicy::submit_compile(methodHandle mh, int bci, CompLevel level, JavaThread* thread) {//....CompileBroker::compile_method(mh, bci, level, mh, hot_count, "tiered", thread);}
}
//CompilerThread表示一个专门执行后台编译的线程
//hospot src/share/vm/runtime/thread.hpp
//以下是其初始化代码,默认执行compiler_thread_entry
static void compiler_thread_entry(JavaThread* thread, TRAPS) {//校验当前线程必须是CompilerThreadassert(thread->is_Compiler_thread(), "must be compiler thread");//循环的从编译队列中获取编译任务执行编译CompileBroker::compiler_thread_loop();
}// Create a CompilerThread
CompilerThread::CompilerThread(CompileQueue* queue, CompilerCounters* counters)
//这里的compiler_thread_entry是一个方法指针,是该线程启动后自动执行的方法
: JavaThread(&compiler_thread_entry) {_env = NULL;_log = NULL;_task = NULL;_queue = queue;_counters = counters;_buffer_blob = NULL;_scanned_nmethod = NULL;_compiler = NULL;//设置ResourceArea的类型resource_area()->bias_to(mtCompiler);#ifndef PRODUCT_ideal_graph_printer = NULL;
#endif
}JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread(){if (TraceThreadEvents) {tty->print_cr("creating thread %p", this);}//初始化JavaThread的各项属性initialize();//正常的Java线程都是_not_attaching_via_jni,只有本地线程才是_attached_via_jni_jni_attach_state = _not_attaching_via_jni;//设置线程的调用方法set_entry_point(entry_point);//判断ThreadTypeos::ThreadType thr_type = os::java_thread;thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread ://创建一个新的线程 os::java_thread;os::create_thread(this, thr_type, stack_sz);}
//hotspot src/share/vm/code/nmethod.hpp
//用于表示一个编译后的Java方法
class nmethod : public CodeBlob {//其初始化是从代码缓存申请空间void* nmethod::operator new(size_t size, int nmethod_size) throw() {return CodeCache::allocate(nmethod_size);}
}
//表示编译队列中的一个编译任务,用来装载编译请求
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileTask : public CHeapObj<mtCompiler> {void CompileTask::set_code(nmethod* nm) {if (_code_handle == NULL && nm == NULL) return;//校验_code_handle不为空guarantee(_code_handle != NULL, "");//最终回调Method:set_code_code_handle->set_code(nm);if (nm == NULL) _code_handle = NULL; // drop the handle also}
}//表示CompileTask队列
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileQueue : public CHeapObj<mtCompiler> {}//编译器的基类,定义了获取编译器相关属性的公共方法
//hospot src/share/vm/compiler/abstractCompiler.hpp
class AbstractCompiler : public CHeapObj<mtCompiler> {//基子类情况//Compiler就是C1编译器即client编译器,//hospot src/share/vm/c1/c1_Compiler.hpp//c1目录下所有类都是该编译器的实现;//C2Compiler就是C2编译器即server编译器,//hospot src/share/vm/opto/c2Compiler.hpp中//opto目录所有类都是该编译器的相关实现;//SharkCompiler就是新的基于LLVM架构的编译器,//hospot src/share/vm/shark/sharkCompiler.hpp//shark目录下相关类都是该编译器的相关实现,因为SharkCompiler目前是基于老旧的LLVM 3.x开发的,很长时间都未更新且存在明显性能问题,所以OpenJDK计划将其完全移除。
}//编译统一对外类
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileBroker: AllStatic {void CompileBroker::compilation_init() {//...//计算出c1,c2队列个数int c1_count = CompilationPolicy::policy()->compiler_count(CompLevel_simple);int c2_count = CompilationPolicy::policy()->compiler_count(CompLevel_full_optimization);}//创建编译器if (c1_count > 0) {_compilers[0] = new Compiler();}if (c2_count > 0) {_compilers[1] = new C2Compiler();}//任务队列,线程初始化init_compiler_threads(c1_count, c2_count);//...}void CompileBroker::compiler_thread_loop() {//...//一直循环while (!is_compilation_disabled_forever()) {//....//从队列中取任务CompileTask* task = queue->get();if (task == NULL) {continue;}//执行任务//创建nmethod//最终会反调用Method::set_codeinvoke_compiler_on_method(task);}}
}
JIT如何运行
使用-Xint解释器模式关闭JIT
- 通过Arguments类,解析-Xint参数
UseCompiler = false;
- 结合《JVM方法调用》generate_normal_entry方法
inc_counter = false
关闭了对方法计数统计,及超标后去提交统计的可能
使用-Xmixed混合模式的JIT调用过程
- 通过Arguments类,解析-Xmixed参数
UseCompiler = true;
ProfileInterpreter = false;
- 初始化编译阀值等待参数
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize
InvocationCounter::reinitialize
- 初始化方法中的MethodCounters的计数器
方法调用过程都会触发Method中MethodCounters计数器初始化
- 调用generate_counter_incr方法,累加MethodCounters计数器
//获取MethodCounters的_invocation_counter属性的_counter属性的地址const Address invocation_counter(rax,MethodCounters::invocation_counter_offset() +InvocationCounter::counter_offset());__ incrementl(rcx, InvocationCounter::count_increment);
5.判断阀值,是否要提交编译
//比较rcx的值是否超过InterpreterInvocationLimit,如果大于等于则跳转到overflow__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreteoverflowrInvocationLimit));__ jcc(Assembler::aboveEqual, *overflow);
- 代码跳转到generate_counter_overflow,向编译器提交编译请求,
//如果要求栈上替换则返回该方法对应的nmethod,否则返回空,然后提交一个方法编译的任务给后台编译线程nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, NULL, thread);
- 返回解释执行此方法
//恢复rbx中的Method*,method_offset是全局变量__ movptr(rbx, Address(rbp, method_offset)); // restore Method*//跳转到do_continue标签,为解释器地址__ jmp(*do_continue, relocInfo::none);
如何成为热点代码触发编译
程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?运行过程中会被即时编译器编译的“热点代码”有两类:
1、被多次调用的方法(同上)。
2、被多次执行的循环体(主要goto指令模板中)。当某个方法往回跳转的次数即循环的次数超过阈值会触发方法的即时编译,并且用编译后的本地代码替换掉原来的字节码指令,所谓的栈上替换就是替换调用入口地址,将原来解释器上的变量,monitor等迁移到编译后的本地代码对应的栈帧中《OSR(On-Stack Replacement )》
方法的编译策略
- 初始化策略及编译环境(线程,队列,编译器)
Threads::create_vm()
init_globals()
compilationPolicy_init()
Threads::create_vm()
CompileBroker::compilation_init
方法调用进通过CompilationPolicy::policy()->event()触发
CompilationPolicy通过编译发法调用次数,及c1,c2队列度等信息调解编译参数,并提交
具体调解过程略
CompileBroker::compile_method(mh, bci, level, mh, hot_count, “tiered”, thread);
- 将方法编译请求封装成task,加入对列
CompileBroker::compile_method
CompileBroker::compile_method_base
CompileBroker::create_compile_task
CompileQueue::add
- 通过CompilerThread循环从CompileQueue取任务,编译,反调Method::set_code
编译方法的执行
1.方法的链接过程对Method对象初始化
c2i_entry//从本地代码执行跳转到字节码解释执行
i2i_entry//字节码解释执行的入口地址
_from_compiled_entry = c2i_entry
_from_interpreted_entry = i2i_entry
2.编译器调用set_code方式安装编译代码
//即其他本地方法调用该方法从c2i_entry变成了直接的起始地址,因为从本地代码中调用该方法的本地代码,不涉及栈帧状态转换
_from_compiled_entry = code->verified_entry_point();//编译代码的起始地址,//Java方法调用该方法的入口地址从正常的_i2i_entry变成了adaper的i2c_entry,因为此时是从字节码执行切换到本地代码执行
_from_interpreted_entry = = mh->get_i2c_entry(); // 字节码执行切换到本地代码执行
- 《JVM方法调用》方法调用使用
address entry_point = method->from_interpreted_entry();
call entry_point
主要参考
《hotspot实战》
《Hotspot 字节码执行与栈顶缓存实现 源码解析》
《Hotspot 方法调用之TemplateInterpreter 源码解析》
《Hotspot 方法调用之Interpreter 源码解析》
《Hotspot 热点代码编译和栈上替换 源码解析》
《Hotspot 编译代码的安装与卸载 源码解析》
《Hotspot 方法编译之CompileTask 源码解析》
《Hotspot 方法编译之CompilationPolicy 源码解析》
hotspot解释器和JIT相关推荐
- 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器
执行引擎概述 执行引擎是Java虛拟机核心的组成部分之一. "虚拟机"是-一个相对于"物理机"的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接 ...
- 第 11 章 一 执行引擎概述、解释器、JIT编译器-热点代码优化
第 11 章 执行引擎 Java到底是编译型语言还是解释型语言? 编译程序基本原理 首先Java通过源码编译器 Javac命令将源代码编译为字节码文件(.class文件), 字节码这种二进制流的文件不 ...
- java 解释器与JIT编译器
早在Java1.0版本的时候,Sun公司发布了一款名为Sun Classic VM的Java虚拟机,它同时也是世界上第一款商用Java虚拟机,在当时这款虚拟机内部只提供解释器,用今天的眼光来看待必然是 ...
- 解释器与JIT编译器
解释器工作机制 解释器真正意义上所承担的角色就是一个运行时"翻译者",将字节码文件中的内容"翻译"为对应平台的本地机器指令执行. 当一条字节码指令被解释执行完成 ...
- 解释器和JIT编译器
解释器 1.初衷:单纯的实现Java程序的跨平台,避免采用静态编译将其转化为机器指令,运行时采用逐行解释字节码并执行程序. 2.工作机制 (1)担任翻译者,将字节码文件中的内容翻译为机器指令并执行 ( ...
- java的编译器、解释器和JIT编译器(转载)
这篇我们来聊聊java的编译器和解释器.先看看官方的解释: Java Compiler (Java 编译器) Java compiler reads source files written in t ...
- 执行引擎、解释器、编译器、JIT编译器的恩怨情仇
学习宋红康老师的JVM课程已经有一段时间了,学习过程中发现,有些内容遗忘得很快,虽然自己也用印象笔记记录了,但是如果没有输出,知识仍然不能完全地消化.因此,决定在JVM专栏中记录和总结学过的内容,欢迎 ...
- 深入理解JVM之JIT编译器(二)
上篇是分析了一下前段编译器,主要过程完成从java代码到字节码的转变,它的改进顶多是提高程序的编码速度和效率.本篇尝试探索JIT编译器,它能够完成从字节码到本地机器码的转变,从而真正的影响程序的运行效 ...
- 【深入Java虚拟机JVM 05】HotSpot对象探秘
说明:文章所有内容均摘自<深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)> 介绍完Java虚拟机的运行时数据区之后,我们大致知道了虚拟机内存的概况,读者了解了内存中放了些什么后 ...
最新文章
- nginx如何解决超长请求串
- [转]VSTO Office二次开发RibbonX代码结构
- eeglab教程系列(4)-绘制通道光谱图
- hdu 1811(拓扑排序+并查集)
- 《周四橄榄球之夜》流媒体视频拆解:Twitch VS Amazon Prime
- 【DirectX12】1.基本组件创建和绘图流程
- 分区供水条件口诀_经典口诀2020年一建市政管道篇
- 30个基于jQuery的日期时间选择插件
- [css] 如何实现换肤功能?
- python读取usb扫码枪数据_vue扫码枪input接收数据
- 2017.5.12 校门外的区间 思考记录
- MySQL 两个死锁样例
- error40无法打开到sql_技术分享|初识SQL优化之执行计划查看分析
- GeoTools——shp转geojson
- 关于sql的正则表达式
- 迭代器、生成器、函数递归与二分法
- 003.ASP.NET MVC集中管理Session
- web前端之HTML常用标签
- 财会法规与职业道德【10】
- 如何禁止搜索引擎收录WordPress站点某个分类的文章?
热门文章
- 【GANs学习笔记】(十九)CycleGAN、StarGAN
- web实现电脑屏幕和手机屏幕适应
- java使用itextpdf生成PDF批量打印荣誉证书(指定位置输出文字)
- 百度网盘将推出青春版:不限下载速度,只有 10GB 免费存储空间
- TCP三次握手四次挥手
- 什么是轻量应用服务器
- 可视化丨福尔摩斯探案集的数据分析
- ModBus(RTU TCP UDP通信)及利用socket通信(DTU)实现Modbus-RTU通信协议
- 矿大课表ics文件生成小工具
- HttpException: 503: Server Error for url:http://cic-1:9696/v2.0/security-groups, Service Unavailable