第61篇-安装与卸载编译方法和OSR
如果方法或OSR完成编译后,还需要安装到对应的位置才能执行。
可以看一下Compilation::compile_method()函数,最重要的实现代码如下:
// compile method int frame_size = compile_java_method(); // ... if (InstallMethods) { // InstallMethods选项默认的值为true install_code(frame_size); }
调用compile_java_method()函数编译,这个函数是C1编译器或C2编译器的入口方法,后面在介绍C1编译器时会从这个函数的实现开始介绍。编译完成后调用install_code()函数“安装”代码。install_code()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/c1/c1_Compilation.cpp void Compilation::install_code(int frame_size) { _env->register_method( method(), osr_bci(), &_offsets, in_bytes(_frame_map->sp_offset_for_orig_pc()), code(), in_bytes(frame_map()->framesize_in_bytes()) / sizeof(intptr_t), debug_info_recorder()->_oopmaps, exception_handler_table(), implicit_exception_table(), compiler(), _env->comp_level(), has_unsafe_access(), SharedRuntime::is_wide_vector(max_vector_size()) ); }
其中_env变量在Compilation类中的定义如下:
ciEnv* _env;
也就是调用ciEnv中的register_method()函数注册编译的方法(热点代码块也是以方法为单位编译的)。ciEnv相当于执行编译任务的一个中间类,充当编译器、后台编译线程和HotSpot VM运行时环境之间的桥梁。
调用的register_method()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/ci/ciEnv.cpp void ciEnv::register_method( ciMethod* target, int entry_bci, CodeOffsets* offsets, int orig_pc_offset, CodeBuffer* code_buffer, int frame_words, OopMapSet* oop_map_set, ExceptionHandlerTable* handler_table, ImplicitExceptionTable* inc_table, AbstractCompiler* compiler, int comp_level, bool has_unsafe_access, bool has_wide_vectors ) { nmethod* nm = NULL; { // ... methodHandle method(THREAD, target->get_Method()); // ... // 创建一个nmethod实例来表示编译后的方法 nm = nmethod::new_nmethod(method, compile_id(), entry_bci, offsets, orig_pc_offset, debug_info(), dependencies(), code_buffer, frame_words, oop_map_set, handler_table, inc_table, compiler, comp_level); // 因为创建nmethod时会将code_buffer中的内容复制到nmethod中,所以这里需要释放CodeBuffer code_buffer->free_blob(); if (nm != NULL) { if (task() != NULL) { task()->set_code(nm); } //如果是非栈上替换 if (entry_bci == InvocationEntryBci) { if (TieredCompilation) { // 如果有之前安装的版本,设置为不可用 nmethod* old = method->code(); if (old != NULL) { old->make_not_entrant(); } } // nmethod安装到Method上,安装完成可以立即执行 method->set_code(method, nm); } else { // 如果是栈上替换 // 将其添加到Klass的osr_nmethods链表上 method->method_holder()->add_osr_nmethod(nm); } } } }
可以看到,会将编译完成后的方法设置到Method::_code属性上,调用add_osr_nmethod()函数将栈上替换的方法添加到_osr_nmethods_head链表上。
在HotSpot VM的实现里,一般一个类被加载进来之后,里面的方法最早也要到它即将第一次被执行的时候才有可能被JIT编译器所编译。在那之前,非abstract非native的Java方法都只是以Java字节码的形式存在,没有对应的机器码(上面所说的 Method::_code属性的值为NULL)
如果在调用方法时,会检测Method::_code属性,如果不为空,则执行编译完成后的方法;对于栈上替换来说,会通过之前介绍的lookup_osr_nmethod_for()函数查找到本次编译的结果,然后执行栈上替换操作。
(1)安装与卸载编译方法
Method::set_code()函数的实现如下:
void Method::set_code(methodHandle mh, nmethod *code) { mh->_code = code; int comp_level = code->comp_level(); if (comp_level > mh->highest_comp_level()) { mh->set_highest_comp_level(comp_level); } OrderAccess::storestore(); mh->_from_compiled_entry = code->verified_entry_point(); OrderAccess::storestore(); // 如果不是MethodHandle的invoke等方法,即编译代码可以立即执行 if (!mh->is_method_handle_intrinsic()){ mh->_from_interpreted_entry = mh->get_i2c_entry(); } }
所谓“安装”其实就是把nmethod与其对应的Method关联起来,把各入口都设置上。设置Method::_code、Method::_from_compiled_entry和Method::_from_interpreted_entry属性。这3个属性对于编译执行与解释执行非常重要,在后面会详细介绍。
与“安装”相反的“卸载”逻辑就是抛弃已编译好的机器码,调用的函数如下:
// 清除编译生成的机器码,返回解释执行 void Method::clear_code() { // _adapter是定义在Method类中的、类型为AdapterHandlerEntry*的变量; if (_adapter == NULL) { _from_compiled_entry = NULL; } else { // 没有编译的版本时,只能回到解释执行 _from_compiled_entry = _adapter->get_c2i_entry(); } OrderAccess::storestore(); _from_interpreted_entry = _i2i_entry; OrderAccess::storestore(); _code = NULL; }
当nmethod被标记成not_entrant或者zombie时,或者执行CodeCache垃圾回收时会调用如上函数卸载编译的方法。
(2)安装与卸载OSR
调用InstanceKlass::add_osr_nmethod()与InstanceKlass::remove_osr_nmethod()函数对OSR进行安装与卸载。
add_osr_nmethod()函数用于将需要执行栈上替换的nmethod实例插入到InstanceKlass的osr_nmethods链表上,函数的实现如下:
void InstanceKlass::add_osr_nmethod(nmethod* n) { // 获取锁 OsrList_lock->lock_without_safepoint_check(); //校验必须是栈上替换方法 assert(n->is_osr_method(), "wrong kind of nmethod"); // 将_osr_nmethods_head设置成n的下一个方法 n->set_osr_link(osr_nmethods_head()); // 将n设置为_osr_nmethods_head set_osr_nmethods_head(n); // 如果使用分层编译 if (TieredCompilation) { Method* m = n->method(); // 更新最高编译级别 m->set_highest_osr_comp_level(MAX2(m->highest_osr_comp_level(), n->comp_level())); } // 解锁 OsrList_lock->unlock(); if (TieredCompilation) { // 查找所有低于nmethod的编译级别的属于同一方法的nmethod实例,将其从osr_nmethods链表上移除 for (int l = CompLevel_limited_profile; l < n->comp_level(); l++) { nmethod *inv = lookup_osr_nmethod(n->method(), n->osr_entry_bci(), l, true); if (inv != NULL && inv->is_in_use()) { inv->make_not_entrant(); } } } } nmethod* osr_nmethods_head() const { return _osr_nmethods_head; };
与add_osr_nmethod()函数相对应的就是remove_osr_nmethod()函数,用于从osr_nmethod链表上移除nmethod,当nmethod被标记成not_entrant或者zombie时,或者执行CodeCache垃圾回收时会调用该函数,函数的实现如下:
void InstanceKlass::remove_osr_nmethod(nmethod* n) { // 校验是否栈上替换方法 assert(n->is_osr_method(), "wrong kind of nmethod"); nmethod* last = NULL; // 获取osr_nmethods链表的头元素 nmethod* cur = osr_nmethods_head(); int max_level = CompLevel_none; // Find the max comp level excluding n Method* m = n->method(); // 遍历osr_nmethods链表直到遇到n,找到n所属的方法的所有nmehtod的最高编译级别 while(cur != NULL && cur != n) { if (TieredCompilation && m == cur->method()) { // Find max level before n max_level = MAX2(max_level, cur->comp_level()); } last = cur; cur = cur->osr_link(); } nmethod* next = NULL; // 如果从链表中找到了目标nmethod if (cur == n) { // 将目标nmethod从链表中移除 next = cur->osr_link(); if (last == NULL) { set_osr_nmethods_head(next); } else { last->set_osr_link(next); } } n->set_osr_link(NULL); if (TieredCompilation) { cur = next; // 遍历链表,更新最大编译级别 while (cur != NULL) { // Find max level after n if (m == cur->method()) { max_level = MAX2(max_level, cur->comp_level()); } cur = cur->osr_link(); } m->set_highest_osr_comp_level(max_level); } }
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流