专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  一个牛逼的国产系统开源了! ·  2 天前  
芋道源码  ·  为什么官方不推荐使用 @Autowired? ·  3 天前  
芋道源码  ·  什么情况,后端的薪资又爆了。。。 ·  6 天前  
51好读  ›  专栏  ›  ImportNew

JVM 模板解释器 – 字节码的 resolve 过程(上)

ImportNew  · 公众号  · Java  · 2017-02-05 21:59

正文

(点击上方公众号,可快速关注)


来源:foreach_break,

www.cnblogs.com/foreach-break/p/hotspot_jvm_resolve_and_link.html

如有好文章投稿,请点击 → 这里了解详情


1、背景


上文探讨了:【JVM】模板解释器–如何根据字节码生成汇编码?(http://www.cnblogs.com/foreach-break/p/jvm-template-interpreter-bytecode-assembly.html)


本篇,我们来关注下字节码的resolve过程。


2、问题及准备工作


上文虽然探讨了字节码到汇编码的过程,但是:


mov %rax,%(rcx,rbx,1) // 0x89 0x04 0x19


其中为什么要指定0×04和0×19呢?


搬出我们的代码:


public int swap2(CallBy a,CallBy b) {

    int t = a.value;

    a.value = b.value;

    b.value  = t;

    return t;

}


换句话讲,我们的汇编代码是要将b.value赋给a.value:


//b.value怎么来的呢?

a.value = b.value


b.value是个整形的field,上述代码的关键字节码是putfield,而模板解释器在初始化的时候(非运行时,这也是模板的意义所在)会调用下面的函数来生成对应的汇编码:


void TemplateTable::putfield_or_static(int byte_no, bool is_static) {

  transition(vtos, vtos);

 

  const Register cache = rcx;

  const Register index = rdx;

  const Register obj   = rcx;

  const Register off   = rbx;

  const Register flags = rax;

  const Register bc    = c_rarg3;

 

  /********************************

  * 关键:这个函数在做什么?

  ********************************/

  resolve_cache_and_index(byte_no, cache, index, sizeof(u2));

 

  jvmti_post_field_mod(cache, index, is_static);

 

  // 上面resolve后,直接从cp cache中对应的entry中就可以获取到field

  load_field_cp_cache_entry(obj, cache, index, off, flags, is_static);

 

  // [jk] not needed currently

  // volatile_barrier(Assembler::Membar_mask_bits(Assembler::LoadStore |

  //                                              Assembler::StoreStore));

 

  Label notVolatile, Done;

  __ movl(rdx, flags);

  __ shrl(rdx, ConstantPoolCacheEntry::is_volatile_shift);

  __ andl(rdx, 0x1);

 

  // field address

  const Address field(obj, off, Address::times_1);

 

  Label notByte, notInt, notShort, notChar,

        notLong, notFloat, notObj, notDouble;

 

  __ shrl(flags, ConstantPoolCacheEntry::tos_state_shift);

 

  assert(btos == 0, "change code, btos != 0");

  __ andl(flags, ConstantPoolCacheEntry::tos_state_mask);

  __ jcc(Assembler::notZero, notByte);

 

  // btos

  // ...

 

  // atos

  // ...

 

  // itos

  {

 

    /***************************************

    *  itos类型,我们的b.value是个整形,

    *  所以对应的机器级别的类型是i,表示整形

    ****************************************/

 

    __ pop(itos);

    if (!is_static) pop_and_check_object(obj);

 

    // 这里就是生成汇编码,也就是上篇博文探讨的主要内容了

    __ movl(field, rax);

 

    if (!is_static) {

      patch_bytecode(Bytecodes::_fast_iputfield, bc, rbx, true, byte_no);

    }

    __ jmp(Done);

  }

 

  __ bind(notInt);

  __ cmpl(flags, ctos);

  __ jcc(Assembler::notEqual, notChar);

 

  // ctos

  // ...

 

  // stos

  // ...

 

  // ltos

  // ...

 

  // ftos

  // ...

 

  // dtos

  // ...

 

  // Check for volatile store

  // ...

}


3、field、class的符号解析及链接


3.1、resolve_cache_and_index


来看看上面代码中的关键点:


// 1. 根据不同的字节码,选择对应的resolve函数.

// 2. 调用resolve函数.

// 3. 根据resolve后的结果,更新寄存器信息,做好衔接.

void TemplateTable::resolve_cache_and_index(int byte_no,

                                            Register Rcache,

                                            Register index,

                                            size_t index_size) {

  const Register temp = rbx;

  assert_different_registers(Rcache, index, temp);

 

  Label resolved;

    assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");

 

    /****************

    * 关键点1

    *****************/

 

    __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);

    __ cmpl(temp, (int) bytecode());  // have we resolved this bytecode?

    __ jcc(Assembler::equal, resolved);

 

  // resolve first time through

  address entry;

  switch (bytecode()) {

  case Bytecodes::_getstatic:

  case Bytecodes::_putstatic:

  case Bytecodes::_getfield:

  case Bytecodes::_putfield:

 

    /****************

    * 关键点2

    *****************/

 

    entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_get_put);

    break;

 

  // ...

 

  default:

    fatal(err_msg("unexpected bytecode: %s", Bytecodes::name(bytecode())));

    break;

  }

 

  // 

  __ movl(temp, (int) bytecode());

  __ call_VM(noreg, entry, temp);

 

  //

  // Update registers with resolved info

  __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);

  __ bind(resolved);

}


上面的代码又有两个关键点:


3.2、get_cache_and_index_and_bytecode_at_bcp


–get_cache_and_index_and_bytecode_at_bcp函数,主要做的一些工作如下文所述。


cp cache指ConstantPoolCache,注意这不是一个一般意义上的缓存,其目的是用于解释器执行时,对字节码进行resolve的。


  1. 对给定的bytecode,在cp cache中查找是否已经存在,如果不存在要进行resolve.至于cp cache问题,最后再说。


  2. 进行resolve的主要内容:

    – InterpreterRuntime::resolve_get_put

    – InterpreterRuntime::resolve_invoke

    – InterpreterRuntime::resolve_invokehandle

    – InterpreterRuntime::resolve_invokedynamic


3.3、resolve_get_put


因为我们的putfield字节码会选择函数resolve_get_put来进行resolve,来关注这个过程:


IRT_ENTRY(void, InterpreterRuntime::resolve_get_put(JavaThread* thread, Bytecodes::Code bytecode))

  // resolve field

  fieldDescriptor info;

  constantPoolHandle pool(thread, method(thread)->constants());

  bool is_put    = (bytecode == Bytecodes::_putfield  || bytecode == Bytecodes::_putstatic);

  bool is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic);

 

  {

    JvmtiHideSingleStepping jhss(thread);

 

    /*******************

    * 关键点

    ********************/

 

    LinkResolver::resolve_field_access(info, pool, get_index_u2_cpcache(thread, bytecode),

                                       bytecode, CHECK);

  } // end JvmtiHideSingleStepping

 

  // check if link resolution caused cpCache to be updated

  if (already_resolved(thread)) return;

 

  // compute auxiliary field attributes

  TosState state  = as_TosState(info.field_type());

 

  Bytecodes::Code put_code = (Bytecodes::Code)0;

 

  InstanceKlass* klass = InstanceKlass::cast(info.field_holder());

  bool uninitialized_static = ((bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic) &&

                               !klass->is_initialized());

  Bytecodes::Code get_code = (Bytecodes::Code)0;

 

  if (!uninitialized_static) {

    get_code = ((is_static) ? Bytecodes::_getstatic : Bytecodes::_getfield);

    if (is_put || !info.access_flags().is_final()) {

      put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield);

    }

  }

 

  // 设置cp cache entry

  // 1. field的存/取字节码.

  // 2. field所属的InstanceKlass(Java类在VM层面的抽象)指针.

  // 3. index和offset

  // 4. field在机器级别的类型状态.因为机器级别只有i(整)、a(引用)、v(void)等类型,这一点也可以帮助理解为什么解释器在生成汇编代码时,需要判断tos.

  // 5. field是否final的.

  // 6. field是否volatile的.

  // 7. 常量池的holder(InstanceKlass*类型).

  cache_entry(thread)->set_field(

    get_code,

    put_code,

    info.field_holder(),

    info.index(),

    info.offset(),

    state,

    info.access_flags().is_final(),

    info.access_flags().is_volatile(),

    pool->pool_holder()

  );

IRT_END


注意tos这个点:


其中,tos是指 T op– O f– S tack,也就是操作数栈(vm实现中是expression stack)顶的东东的类型.


上面的代码中又标出一个关键点:


3.4、resolve_field_access


看代码:


// 对field进行resolve,并检查其可访问性等信息

void LinkResolver::resolve_field_access(fieldDescriptor& result, constantPoolHandle pool, int index, Bytecodes::Code byte, TRAPS) {

  // Load these early in case the resolve of the containing klass fails

 

  // 从常量池中获取field符号

  Symbol* field = pool->name_ref_at(index);

 

  // 从常量池中获取field的签名符号

  Symbol* sig   = pool->signature_ref_at(index);

 

  // resolve specified klass

  KlassHandle resolved_klass;

 

  // 关键点1

  resolve_klass(resolved_klass, pool, index, CHECK);

 

  // 关键点2

  KlassHandle  current_klass(THREAD, pool->pool_holder());

  resolve_field(result, resolved_klass, field, sig, current_klass, byte, true, true, CHECK);

}


注意到上面的代码还调用了resolve_klass和resolve_field,我们一个一个看,


觉得本文对你有帮助?请分享给更多人

关注「ImportNew」,看技术干货