• 【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过程。

    终。

  • 相关阅读:
    Winform 切换语言 实现多语言版本
    PowerDesigner导出表到word
    【SQL】两个带order by查询进行union all报ORA-00933错误的解决方法
    读写txt文件
    c# 进度条的使用(例子)、线程
    设计模式——策略模式
    设计模式——简单工厂模式
    解决JSP路径问题的方法(jsp文件开头path, basePath作用)
    反射
    Struts2中的valuestack
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7048118.html
Copyright © 2020-2023  润新知