JVM之java方法调用入口entry_point的生成

2,758 阅读9分钟

接着前面文章介绍了JVM通过JavaCalls::call方法,接着调用call_helper,然后调用CallStub调用java的方法的,CallStub结构体中entry_point是Java方法的入口地址,它是在哪里生成的呢? 那么今天主要来分析下模板解释器的入口地址,关于JIT编译器暂时先看. 可以看到JavaCalls::call_help中是通过method的_from_interpreted_entry字段作为method的方法入口地址.那么方法入口地址是在哪里设置的呢?就是方法链接的时候进行设置方法执行入口地址。

void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
   //省略
  address entry_point = method->from_interpreted_entry();
 }

method的链接

method.cpp中link_method就是方法的链接工作,

  1. 判断_i2i_entry如果已经存在,则表示已经链接,则直接返回。
  2. Interpreter::entry_for_method的获取编译器的入口地址
  3. set_interpreter_entry设置_from_interpreted_entry,_i2i_entry这个两个字段值为第二步获取方法入口地址.
  4. make_adapters做适配器,是为了解释器和编译器之间转换.
void Method::link_method(methodHandle h_method, TRAPS) {
  if (_i2i_entry != NULL) return;
  
  address entry = Interpreter::entry_for_method(h_method);
  assert(entry != NULL, "interpreter entry must be non-null");
  // Sets both _i2i_entry and _from_interpreted_entry
  set_interpreter_entry(entry);

  // Don't overwrite already registered native entries.
  if (is_native() && !has_native_function()) {
    set_native_function(
      SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
      !native_bind_event_is_interesting);
  }
 
  (void) make_adapters(h_method, CHECK);
}

下面是Interpreter父类AbstractInterpreter的entry_for_method方法,

  1. 调用entry_for_kind获取方法类型.
  2. 通过方法类型去_entry_table获取对应的方法入口地址.
 AbstractInterpreter{
  static address    entry_for_kind(MethodKind k)                { 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)); }
}

解释器的生成方法_entry_table入口表

首先看下发方法类型的枚举如下,JVM为以下每一种类型方法生成不同的方法入口指令的入口地址.我们先主要看zerolocal类型就是不同的java方法入口.

 enum MethodKind {
    zerolocals,                                                 // method needs locals initialization
    zerolocals_synchronized,                                    // method needs locals initialization & is synchronized
    native,                                                     // native method
    native_synchronized,                                        // native method & is synchronized
    empty,                                                      // empty method (code: _return)
    accessor,                                                   // accessor method (code: _aload_0, _getfield, _(a|i)return)
    abstract,                                                   // abstract method (throws an AbstractMethodException)
    method_handle_invoke_FIRST,                                 // java.lang.invoke.MethodHandles::invokeExact, etc.
    method_handle_invoke_LAST                                   = (method_handle_invoke_FIRST
                                                                   + (vmIntrinsics::LAST_MH_SIG_POLY
                                                                      - vmIntrinsics::FIRST_MH_SIG_POLY)),
    java_lang_math_sin,                                         // implementation of java.lang.Math.sin   (x)
    java_lang_math_cos,                                         // implementation of java.lang.Math.cos   (x)
    java_lang_math_tan,                                         // implementation of java.lang.Math.tan   (x)
    java_lang_math_abs,                                         // implementation of java.lang.Math.abs   (x)
    java_lang_math_sqrt,                                        // implementation of java.lang.Math.sqrt  (x)
    java_lang_math_log,                                         // implementation of java.lang.Math.log   (x)
    java_lang_math_log10,                                       // implementation of java.lang.Math.log10 (x)
    java_lang_math_pow,                                         // implementation of java.lang.Math.pow   (x,y)
    java_lang_math_exp,                                         // implementation of java.lang.Math.exp   (x)
    java_lang_ref_reference_get,                                // implementation of java.lang.ref.Reference.get()
    java_util_zip_CRC32_update,                                 // implementation of java.util.zip.CRC32.update()
    java_util_zip_CRC32_updateBytes,                            // implementation of java.util.zip.CRC32.updateBytes()
    java_util_zip_CRC32_updateByteBuffer,                       // implementation of java.util.zip.CRC32.updateByteBuffer()
    number_of_method_entries,
    invalid = -1
  };

方法入口表是宏定义中生成


#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 kinds
  method_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)

  initialize_method_handle_entries();
#undef method_entry

上面宏定义中会调用父类的generate_method_entry方法生成方法入口.

address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter::MethodKind kind) {
  // determine code generation flags
  bool 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;

    case Interpreter::java_lang_math_sin     : // fall thru
    case Interpreter::java_lang_math_cos     : // fall thru
    case Interpreter::java_lang_math_tan     : // fall thru
    case Interpreter::java_lang_math_abs     : // fall thru
    case Interpreter::java_lang_math_log     : // fall thru
    case Interpreter::java_lang_math_log10   : // fall thru
    case Interpreter::java_lang_math_sqrt    : // fall thru
    case Interpreter::java_lang_math_pow     : // fall thru
    case Interpreter::java_lang_math_exp     : entry_point = ig_this->generate_math_entry(kind);      break;
    case Interpreter::java_lang_ref_reference_get
                                             : entry_point = ig_this->generate_Reference_get_entry(); break;
    case Interpreter::java_util_zip_CRC32_update
                                             : entry_point = ig_this->generate_CRC32_update_entry();  break;
    case Interpreter::java_util_zip_CRC32_updateBytes
                                             : // fall thru
    case Interpreter::java_util_zip_CRC32_updateByteBuffer
                                             : entry_point = ig_this->generate_CRC32_updateBytes_entry(kind); break;
    default:
      fatal(err_msg("unexpected method kind: %d", kind));
      break;
  }
  if (entry_point) return entry_point;
  return ig_this->generate_normal_entry(synchronized);
}

下面是generate_normal_entry方法体(这个是templateInterpreter的32位平台的生成逻辑)。

address InterpreterGenerator::generate_normal_entry方法提。(bool synchronized) {
  bool inc_counter  = UseCompiler || CountCompiledCalls;

  // rbx,: Method*
  // rsi: sender sp
  address entry_point = __ pc();

  const Address constMethod       (rbx, Method::const_offset());
  const Address access_flags      (rbx, Method::access_flags_offset());
  const Address size_of_parameters(rdx, ConstMethod::size_of_parameters_offset());
  const Address size_of_locals    (rdx, ConstMethod::size_of_locals_offset());

  // get parameter size (always needed)
  __ movptr(rdx, constMethod);
  __ load_unsigned_short(rcx, size_of_parameters);
  // rbx,: Method*
  // rcx: size of parameters
  // rsi: sender_sp (could differ from sp+wordSize if we were called via c2i )
  __ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
  __ subl(rdx, rcx);    // rdx = no. of additional locals

  // see if we've got enough room on the stack for locals plus overhead.
  generate_stack_overflow_check();

  // get return address
  __ pop(rax);

  // compute beginning of parameters (rdi)
  __ lea(rdi, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));

  // rdx - # of additional locals
  // allocate space for locals
  // explicitly initialize locals
  {
    Label exit, loop;
    __ testl(rdx, rdx);
    __ jcc(Assembler::lessEqual, exit);               // do nothing if rdx <= 0
    __ bind(loop);
    __ push((int32_t)NULL_WORD);                      // initialize local variables
    __ decrement(rdx);                                // until everything initialized
    __ jcc(Assembler::greater, loop);
    __ bind(exit);
  }
  // initialize fixed part of activation frame
  generate_fixed_frame(false);

  // Since at this point in the method invocation the exception handler
  // would try to exit the monitor of synchronized methods which hasn't
  // been entered yet, we set the thread local variable
  // _do_not_unlock_if_synchronized to true. The remove_activation will
  // check this flag.
  __ get_thread(rax);
  const Address do_not_unlock_if_synchronized(rax,   in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
  __ movbool(do_not_unlock_if_synchronized, true);

  __ profile_parameters_type(rax, rcx, rdx);
  // increment invocation count & check for overflow
  Label invocation_counter_overflow;
  Label profile_method;
  Label profile_method_continue;
  if (inc_counter) {
    generate_counter_incr(&invocation_counter_overflow, &profile_method, &profile_method_continue);
    if (ProfileInterpreter) {
      __ bind(profile_method_continue);
    }
  }
  Label continue_after_compile;
  __ bind(continue_after_compile);

  bang_stack_shadow_pages(false);

  // reset the _do_not_unlock_if_synchronized flag
  __ get_thread(rax);
  __ movbool(do_not_unlock_if_synchronized, false);
  
  // 开始分派执行java的字节码指令
  __ dispatch_next(vtos);

  // invocation counter overflow
  if (inc_counter) {
    if (ProfileInterpreter) {
      // We have decided to profile this method in the interpreter
      __ bind(profile_method);
      __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::profile_method));
      __ set_method_data_pointer_for_bcp();
      __ get_method(rbx);
      __ jmp(profile_method_continue);
    }
    // Handle overflow of counter and compile method
    __ bind(invocation_counter_overflow);
    generate_counter_overflow(&continue_after_compile);
  }
    // jvmti support
  __ notify_method_entry();

  // 分派执行执行
  __ dispatch_next(vtos);

  return entry_point;
}
  1. __ pc() 方法,前面文章已经介绍过__它是宏定义,实际是InterpreterMacroAssembler的指针,就调用其父类AbstractAssembler的 pc()方法获取CodeSection对象的end地址.
class AbstractAssembler : public ResourceObj  {
 protected:
  CodeSection* _code_section;   
  
 address  pc()   const   { 
         return   code_section()->end();
      }
}
  1. 之前介绍过在调用方法入口前的寄存器rbx存的mthod的指针,rsi是调用者的rsp地址,下面这个行就是将constMethod的地址移动到rdx寄存器,
 __ movptr(rdx, constMethod);
  1. 然后将参数大小的值加载到rcx寄存器中.
__ load_unsigned_short(rcx, size_of_parameters);
  1. 将局部变量表的大小移加载到rdx寄存器
__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
  1. 将rdx的值减去rcx寄存器的值,就等于除了参数以外的额外的本地变量个数,将剩余的需要额外的参数个数值保存到rdx寄存器.
 __ subl(rdx, rcx);  
  1. 校验栈溢出
generate_stack_overflow_check();
  1. 将栈上返回地址保存到rax寄存器中.
  __ pop(rax);
  1. 将栈顶的地址向高地址偏移参数大小*栈中一个元素大小32位就是4byte),然后向下移动一个机器字长(32位也就是4byte),此时栈顶的位置就是执行第一个参数的位置.
__ lea(rdi, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));
  1. 接下者这段代码使用循环初始化本地变量表。\
  • 首先定义Label标签 exit、loop.
  • 然后将rdx与自己做and计算,判断rdx的数量小于0,说明不需要本地变量表,则直接跳转到exit标签.
  • 如果rdx的值大于0,则说明需要初始化本地变量表, 然后在此插入一个loop标签
  • 执行push操作,将NULL_WORD在GNUCi标准就是0,然后转换成32为int指针,实际就是本地变量初始化空的指针.
  • 将rdx的值减1。
  • 如果rdx的值大于0,则跳转bind(loop)的位置循环执行,直到所有的本地变量表都初始化完成.
 {
    Label exit, loop;
    __ testl(rdx, rdx);
    // do nothing if rdx 
    __ jcc(Assembler::lessEqual, exit); <= 0
    __ bind(loop);
      // initialize local 
    __ push((int32_t)NULL_WORD); 
       // until everything initialized
    __ decrement(rdx);   
    __ jcc(Assembler::greater, loop);
    __ bind(exit);
  }
  1. 接下来就是生成java方法固定栈帧,这个方法先了解其作用,会在一篇单独细分析。
  // initialize fixed part of activation frame
  generate_fixed_frame(false);
  1. 将当前执行方法的线程对象移动rax寄存器,获取JavaThread线程的_do_not_unlock_if_synchronized的偏移,然后设置该线程本地变量为true。因为此时方法synchonized枷锁操作,由于此时还没有执行enter的操作,所以是不能执行unlock操作,所以在remove_activation的时候会检查这个标识.
  __ get_thread(rax);
  const Address do_not_unlock_if_synchronized(rax,     in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
  __ movbool(do_not_unlock_if_synchronized, true);
  1. 接下来这段就是进行方法调用次数统计,这里就不详细介绍。
 __ profile_parameters_type(rax, rcx, rdx);
  // increment invocation count & check for overflow
  Label invocation_counter_overflow;
  Label profile_method;
  Label profile_method_continue;
  if (inc_counter) {
    generate_counter_incr(&invocation_counter_overflow, &profile_method, &profile_method_continue);
    if (ProfileInterpreter) {
      __ bind(profile_method_continue);
    }
  }
  Label continue_after_compile;
  __ bind(continue_after_compile);

  bang_stack_shadow_pages(false);
  1. jvmti的回调函数支持.这里也就不详细说
  // jvmti support
  __ notify_method_entry();
  1. 这个就是Java执行字节码执行指令的关键入口。这个会在下面的文章介绍.
  __ dispatch_next(vtos);

总结
本文主要分析了JVM生成java的普通方法的方法入口entry_point的汇编指令的代码的过程,可以了解JVM生成java栈帧的产地的参数是保存在调用者的栈帧中,方法内部的局部变量会初始化到java的栈帧中,还有JVM中定义栈帧动态链接地址和操作数栈还没有分析,以及是如何执行字节码的方法的code的字节码指令等,后面再分析。