zoukankan      html  css  js  c++  java
  • 【JVM】模板解释器--字节码的resolve过程

    1、背景

    上文探讨了:【JVM】模板解释器–怎样依据字节码生成汇编码?

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

    2、问题及准备工作

    上文尽管探讨了字节码到汇编码的过程,可是:

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

    当中为什么要指定0x04和0x19呢?

    搬出我们的代码:

    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_klassresolve_field。我们一个一个看,

    3.5、resolve_klass:

    // resolve klass
    void LinkResolver::resolve_klass(KlassHandle& result, constantPoolHandle pool, int index, TRAPS) {
      Klass* result_oop = pool->klass_ref_at(index, CHECK);
      result = KlassHandle(THREAD, result_oop);
    }

    上面的代码非常easy,从常量池取出相应的klass,并同当前线程一起。封装为一个KlassHandle。

    3.6、resolve_field:

    再接着看resolve_field:

    // field的解析及链接
    // 此过程将完毕:
    //
    //   1. field的可訪问性验证.
    //   2. field所属的类的可訪问性验证.
    //   3. field所属的类的ClassLoaderData及当前运行的方法(Method)所属的类的ClassLoaderData的验证.
    //   4. field所属的类中,假设对其他的类有依赖,要进行装载、解析和链接,假设没有找到。比方classpath中不包括。那么就报相似ClassDefNotFoundError的异常.
    //    假设Jar包冲突。也在这里检測到。并报异常.
    //    假设field所属的类。及其依赖的类都找到了。那么将ClassLoaderData的约束constraint进行合并.
    //   5. 当前正在调用的方法的签名。从callee角度和caller角度来比較是否一致.
    
    // 关于classLoader的问题,兴许文章再展开吧。不是一句两句能说的清。

    void LinkResolver::resolve_field(fieldDescriptor& fd, KlassHandle resolved_klass, Symbol* field, Symbol* sig, KlassHandle current_klass, Bytecodes::Code byte, bool check_access, bool initialize_class, TRAPS) { assert(byte == Bytecodes::_getstatic || byte == Bytecodes::_putstatic || byte == Bytecodes::_getfield || byte == Bytecodes::_putfield || (byte == Bytecodes::_nop && !check_access), "bad field access bytecode"); bool is_static = (byte == Bytecodes::_getstatic || byte == Bytecodes::_putstatic); bool is_put = (byte == Bytecodes::_putfield || byte == Bytecodes::_putstatic); // Check if there's a resolved klass containing the field if (resolved_klass.is_null()) { ResourceMark rm(THREAD); THROW_MSG(vmSymbols::java_lang_NoSuchFieldError(), field->as_C_string()); } /************************ * 关键点1 *************************/ // Resolve instance field KlassHandle sel_klass(THREAD, resolved_klass->find_field(field, sig, &fd)); // check if field exists; i.e., if a klass containing the field def has been selected if (sel_klass.is_null()) { ResourceMark rm(THREAD); THROW_MSG(vmSymbols::java_lang_NoSuchFieldError(), field->as_C_string()); } if (!check_access) // Access checking may be turned off when calling from within the VM. return; /************************ * 关键点2 *************************/ // check access check_field_accessability(current_klass, resolved_klass, sel_klass, fd, CHECK); // check for errors if (is_static != fd.is_static()) { // ... THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), msg); } // Final fields can only be accessed from its own class. if (is_put && fd.access_flags().is_final() && sel_klass() != current_klass()) { THROW(vmSymbols::java_lang_IllegalAccessError()); } // initialize resolved_klass if necessary // note 1: the klass which declared the field must be initialized (i.e, sel_klass) // according to the newest JVM spec (5.5, p.170) - was bug (gri 7/28/99) // // note 2: we don't want to force initialization if we are just checking // if the field access is legal; e.g., during compilation if (is_static && initialize_class) { sel_klass->initialize(CHECK); } if (sel_klass() != current_klass()) { HandleMark hm(THREAD); Handle ref_loader (THREAD, InstanceKlass::cast(current_klass())->class_loader()); Handle sel_loader (THREAD, InstanceKlass::cast(sel_klass())->class_loader()); { ResourceMark rm(THREAD); /************************ * 关键点3 *************************/ Symbol* failed_type_symbol = SystemDictionary::check_signature_loaders(sig, ref_loader, sel_loader, false, CHECK); if (failed_type_symbol != NULL) { // ... THROW_MSG(vmSymbols::java_lang_LinkageError(), buf); } } } // return information. note that the klass is set to the actual klass containing the // field, otherwise access of static fields in superclasses will not work. }

    上面的代码,我们梳理出三个跟本主题相关的关键点,已在凝视中标出。我们来看:

    // 关键点1 :
    // 获取field所属的类或接口相应的klass,或者NULL。假设是NULL就抛异常了
    KlassHandle sel_klass(THREAD, resolved_klass->find_field(field, sig, &fd));
    
    // 1. 假设是resolved_klass中的field。返回resolved_klass
    // 2. 假设1不满足,尝试返回接口或接口的超类(super interface)相应的klass(递归)
    // 3. 假设1、2点都不满足,尝试返回父类或超类相应的klass(递归)或者NULL.
    Klass* InstanceKlass::find_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const {
      // search order according to newest JVM spec (5.4.3.2, p.167).
      // 1) search for field in current klass
      if (find_local_field(name, sig, fd)) {
        return const_cast<InstanceKlass*>(this);
      }
      // 2) search for field recursively in direct superinterfaces
      { Klass* intf = find_interface_field(name, sig, fd);
        if (intf != NULL) return intf;
      }
      // 3) apply field lookup recursively if superclass exists
      { Klass* supr = super();
        if (supr != NULL) return InstanceKlass::cast(supr)->find_field(name, sig, fd);
      }
      // 4) otherwise field lookup fails
      return NULL;
    }
    
    // 关键点2:
    // 1. resolved_klass来自当前线程所运行的当前方法的当前字节码所属的常量池.
    // 2. sel_klass是field所属的类或接口相应的klass
    // 3. current_klass是常量池所属的klass(pool_holder).
    // 4. 3种klass能够同样。也能够不同.能够想象一个调用链,依赖的各个class.
    check_field_accessability(current_klass, resolved_klass, sel_klass, fd, CHECK);
    
    // 关键点3:
    // ref_loader代表了current_klass的classLoader
    Handle ref_loader (THREAD, InstanceKlass::cast(current_klass())->class_loader());
    // sel_loader代表了sel_klass的classLoader
        Handle sel_loader (THREAD, InstanceKlass::cast(sel_klass())->class_loader());
    // 依据签名符号sig、ref_loader、sel_loader来检查classLoader的约束是否一致,假设不一致就会抛异常。所谓一致不是同样但包括同样的情况,假设一致。那么就合并约束,同一时候还要进行依赖(depedencies)链的维护.
    // 由于内容比較多,本篇不展开.
    Symbol* failed_type_symbol =
            SystemDictionary::check_signature_loaders(sig,
                                                      ref_loader, sel_loader,
                                                      false,
                                                      CHECK);

    上面的关键点解析都在凝视中了,当中有的地方内容太多,不宜在本篇展开。

    那么。怎样获取当前运行的字节码相应的cp cache entry呢?

    3.7、怎样获取cp cache entry:

    关键代码例如以下:

    // 获取当前正在运行的bytecode相应的cp cache entry
    static ConstantPoolCacheEntry* cache_entry(JavaThread *thread) { 
      return cache_entry_at(thread, Bytes::get_native_u2(bcp(thread) + 1)); 
    }
    
    // ↓
    
    // 获取解释器当前的(B)yte (C)ode (P)ointer,也就是当前指令地址,以指针表达
    static address   bcp(JavaThread *thread)           { 
      return last_frame(thread).interpreter_frame_bcp(); 
    }
    
    // ↓
    
    // 获取cp cache entry
    static ConstantPoolCacheEntry* cache_entry_at(JavaThread *thread, int i)  { 
      return method(thread)->constants()->cache()->entry_at(i); 
    }
    
    // ↓
    
    // 获取当前正在运行的方法
    static Method*   method(JavaThread *thread) { 
      return last_frame(thread).interpreter_frame_method(); 
    }
    
    // ↓
    
    // 获取interpreterState->_method,也就是当前正在运行的方法
    Method* frame::interpreter_frame_method() const {
      assert(is_interpreted_frame(), "interpreted frame expected");
      Method* m = *interpreter_frame_method_addr();
      assert(m->is_method(), "not a Method*");
      return m;
    }
    
    // ↓
    
    // 获取interpreterState->_method的地址
    inline Method** frame::interpreter_frame_method_addr() const {
      assert(is_interpreted_frame(), "must be interpreted");
      return &(get_interpreterState()->_method);
    }
    
    // ↓
    
    // 获取interpreterState
    inline interpreterState frame::get_interpreterState() const {
      return ((interpreterState)addr_at( -((int)sizeof(BytecodeInterpreter))/wordSize ));
    }
    
    // ↓
    
    // interpreterState实际是个BytecodeInterpreter型指针
    typedef class BytecodeInterpreter* interpreterState;

    上述过程总结下:

    1、获取bcp,也就是解释器当前正在运行的字节码的地址。以指针形式返回.

    2、bcp是通过当前线程的调用栈的最后一帧来获取的,而且是个解释器栈帧.为什么是最后一帧?

    方法1 栈帧1 
    调用 -> 方法2 栈帧2
    ...
    调用 -> 方法n 栈帧n // 最后一帧

    每一个方法在调用时都会用一个栈帧frame来描写叙述调用的状态信息。最后调用的方法就是当前方法,所以是取最后一帧.

    3、当前方法的地址是通过栈帧中保存的interpreterState来获取的。而这个interpreterState是个BytecodeInterpreter型的解释器,不是模板解释器。

    4、获取到方法的地址后。就能够获取到方法所属的常量池了,接着从常量池相应的cp cache中就能够获取到相应的entry了。

    5、第4点提到相应,怎么个相应法?想象数组的下标。这个下标是什么呢?就是对bcp的一个整形映射。

    3.8、BytecodeInterpreter的一些关键字段

    注意BytecodeInterpreter和TemplateInterpreter不是一码事.

    BytecodeInterpreter的一些关键字段,帮助理解bcp、thread、cp、cp cache在解释器栈帧中意义:

    private:
        JavaThread*           _thread;        // the vm's java thread pointer
        address               _bcp;           // instruction pointer
        intptr_t*             _locals;        // local variable pointer
        ConstantPoolCache*    _constants;     // constant pool cache
        Method*               _method;        // method being executed
        DataLayout*           _mdx;           // compiler profiling data for current bytecode
        intptr_t*             _stack;         // expression stack
        messages              _msg;           // frame manager <-> interpreter message
        frame_manager_message _result;        // result to frame manager
        interpreterState      _prev_link;     // previous interpreter state
        oop                   _oop_temp;      // mirror for interpreted native, null otherwise
        intptr_t*             _stack_base;    // base of expression stack
        intptr_t*             _stack_limit;   // limit of expression stack
        BasicObjectLock*      _monitor_base;  // base of monitors on the native stack

    在进行resolve后,字节码就在ConstantPoolCache相应的Entry中了,下一次再运行就不须要resolve。

    至于BytecodeInterpreter是个什么解释器,和模板解释器有啥关系,后面再说吧。

    4、结语

    本文简要探讨了:

    字节码的resolve过程。

    终。

  • 相关阅读:
    纯CSS打造可折叠树状菜单
    c++ Constructor FAQ 继续
    Java设计模式偷跑系列(六)Singleton模式的建模与实现
    优秀的产品经理是怎样炼成的?
    Pki原则
    屌丝男初中丰富的工作实践反击
    Android Material Design带UI变化
    unity多边形uv地图
    BZOJ 1208 HNOI2004 宠物收容所 平衡树/set
    [连载]Java程序设计(04)---任务驱动的方法:工资结算系统
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7282041.html
Copyright © 2011-2022 走看看