JVM方法调用——java方法到native方法

java方法到native方法

很多时候Java方法也会调用到native方法,比如说常用的arraycopy

@IntrinsicCandidate
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

它所对应的实现是:

JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,
                               jobject dst, jint dst_pos, jint length))
  JVMWrapper("JVM_ArrayCopy");
  // Check if we have null pointers
  if (src == NULL || dst == NULL) {
    THROW(vmSymbols::java_lang_NullPointerException());
  }
  arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));
  arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));
  assert(oopDesc::is_oop(s), "JVM_ArrayCopy: src not an oop");
  assert(oopDesc::is_oop(d), "JVM_ArrayCopy: dst not an oop");
  // Do copy
  s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);
JVM_END

和普通的方法一样,native方法一样会有一个Method对象作为JVM的内部表示,通过对对应的Klass对象的查找就可以找到这个Method对象,不过表示native方法的Method对象略有不同:

Method-object-layout.drawio

Method对象是可变长的,如果Method对象表示的是一个native方法,那么还会在末尾有两个多出来的字段,而非native方法并不会有,这两个字段分别通过Method::native_functionMethod::signature_handler获取。

注册JNI函数

一个问题来了,native是用户自己写的C++函数,那么JVM要怎样才能找到其对应的实现?首先需要加载对应的链接库:

// HelloWorldJNI.java
package a.b
public class HelloWorldJNI {
    static {
        // 加载libnative.so。
        System.loadLibrary("native"); 
    }
    
    public static void main(String[] args) {
        new HelloWorldJNI().sayHello();
    }
    // 需要使用c/c++实现,然后将c/c++文件编译为一个动态链接库,叫做libnative.so。
    private native void sayHello();
}

使用javac -h . H HelloWorldJNI.java会在当前目录生成对应的头文件(如果是java10之前则是使用javah),头文件内容如下:

#include <jni.h>
/* Header for class a_b_HelloWorldJNI */

#ifndef _Included_a_b_HelloWorldJNI
#define _Included_a_b_HelloWorldJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     a_b_HelloWorldJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_a_b_HelloWorldJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

注意上面的Java_a_b_HelloWorldJNI_sayHello,这里会为sayHello生成一个新的名字,格式为Java_{packagePath}_{className}_{functionName},这个其实应该叫做short name,因为还有一个long name,其中还编入了参数有关的信息,因为native函数其实也是可以重载的,为了防止重载的冲突,所以有了long name。

加载了了链接库之后,还要链接库中搜索和方法对应的符号,也就是找到对应符号所在的内存地址。搜索符号有两个方法,第一个方法是让JVM根据native方法的名字自动去获取,这要求native方法符合命名规范。另外一个则是进行注册,这个并没有要求对应的命名规范,第二种方式相对简短,所以我们先来看看第二种,还是看到上面提到过的System类:

public final class System {
    /* Register the natives via the static initializer.
     *
     * The VM will invoke the initPhase1 method to complete the initialization
     * of this class separate from <clinit>.
     */
    private static native void registerNatives();
    static {
        registerNatives();
    }
}

其中声明了一个native方法registerNatives,然后又在静态块中调用了这个方法,也就是说在类加载之后就会调用registerNatives方法,这个native方法定义为:

// src/java.base/share/native/libjava/System.c

#define OBJ "Ljava/lang/Object;"

/* Only register the performance-critical methods */
static JNINativeMethod methods[] = {
    {"currentTimeMillis", "()J",              (void *)&JVM_CurrentTimeMillis},
    {"nanoTime",          "()J",              (void *)&JVM_NanoTime},
    {"arraycopy",     "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy}, // 对应的实现。
};

JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

对于registerNatives方法必须按照命名要求来,因为它本身没有被注册。可以看到它注册了methods中的几个函数,methods是个JNINativeMethod对象数组。那么我们再来看看RegisterNatives方法的具体实现好了,其实非常容易理解,很直观:

// src/hotspot/share/prims/jni.cpp

JNI_ENTRY(jint, jni_RegisterNatives(JNIEnv *env, jclass clazz,
                                    const JNINativeMethod *methods,
                                    jint nMethods))
  JNIWrapper("RegisterNatives");
  HOTSPOT_JNI_REGISTERNATIVES_ENTRY(env, clazz, (void *) methods, nMethods);
  jint ret = 0;
  DT_RETURN_MARK(RegisterNatives, jint, (const jint&)ret);

  Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz));

  for (int index = 0; index < nMethods; index++) {
    const char* meth_name = methods[index].name;
    const char* meth_sig = methods[index].signature;
    int meth_name_len = (int)strlen(meth_name);

    // The class should have been loaded (we have an instance of the class
    // passed in) so the method and signature should already be in the symbol
    // table.  If they're not there, the method doesn't exist.
    TempNewSymbol  name = SymbolTable::probe(meth_name, meth_name_len);
    TempNewSymbol  signature = SymbolTable::probe(meth_sig, (int)strlen(meth_sig));

    if (name == NULL || signature == NULL) {
      ResourceMark rm;
      stringStream st;
      st.print("Method %s.%s%s not found", k->external_name(), meth_name, meth_sig);
      // Must return negative value on failure
      THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), -1);
    }

    bool res = register_native(k, name, signature,
                               (address) methods[index].fnPtr, THREAD);
    if (!res) {
      ret = -1;
      break;
    }
  }
  return ret;
JNI_END

就是把传入的JNINativeMethod对象数组给遍历了一遍,真正进行注册的地方在register_native,去除掉一些处理异常的代码之后唯一需要关注的只有Method::set_native_function

// src/hotspot/share/prims/jni.cpp

static bool register_native(Klass* k, Symbol* name, Symbol* signature, address entry, TRAPS) {
  Method* method = k->lookup_method(name, signature);
  if (method == NULL) {
    ResourceMark rm;
    stringStream st;
    st.print("Method %s name or signature does not match",
             Method::name_and_sig_as_C_string(k, name, signature));
    THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
  }
  if (!method->is_native()) {
    // trying to register to a non-native method, see if a JVM TI agent has added prefix(es)
    method = find_prefixed_native(k, name, signature, THREAD);
    if (method == NULL) {
      ResourceMark rm;
      stringStream st;
      st.print("Method %s is not declared as native",
               Method::name_and_sig_as_C_string(k, name, signature));
      THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
    }
  }

  if (entry != NULL) {
    method->set_native_function(entry,
      Method::native_bind_event_is_interesting);
  } else {
    method->clear_native_function();
  }
  if (PrintJNIResolving) {
    ResourceMark rm(THREAD);
    tty->print_cr("[Registering JNI native method %s.%s]",
      method->method_holder()->external_name(),
      method->name()->as_C_string());
  }
  return true;
}

Method::set_native_function非常简单,就只是设置了一下Method的native_function字段,并没有设置signature_handler字段,这个字段会在后面进行处理。

而让JVM自动搜索实现所在位置则是需要等到native方法被调用的时候才会发生,所以将这个内容推迟到native方法调用的时候再来说。

调用native方法

调用native方法的过程和调用java方法是类似的,只是建立栈帧的方式有些不同,调用java方法来到了generate_normal_entry输出的例程,而调用jni则会来到generate_native_entry生成的例程,在此之前经过的步骤是一样的,所以我们只关注generate_native_entry中生成的代码就行了:

// Interpreter stub for calling a native method. (asm interpreter)
// This sets up a somewhat different looking stack for calling the
// native method than the typical interpreter frame setup.
address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) {
  // determine code generation flags
  bool inc_counter  = UseCompiler || CountCompiledCalls || LogTouchedMethods;

  // rbx: Method*
  // rbcp: 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(rcx, ConstMethod::
                                        size_of_parameters_offset());


  // get parameter size (always needed)
  __ movptr(rcx, constMethod);
  __ load_unsigned_short(rcx, size_of_parameters);

  // native calls don't need the stack size check since they have no
  // expression stack and the arguments are already on the stack and
  // we only add a handful of words to the stack

  // rbx: Method*
  // rcx: size of parameters
  // rbcp: sender sp
  __ pop(rax);                                       // get return address

  // for natives the size of locals is zero

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

  // add 2 zero-initialized slots for native calls
  // initialize result_handler slot
  __ push((int) NULL_WORD);
  // slot for oop temp
  // (static native method holder mirror/jni oop result)
  __ push((int) NULL_WORD);

  // initialize fixed part of activation frame
  generate_fixed_frame(true);


  // 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.

  const Register thread1 = NOT_LP64(rax) LP64_ONLY(r15_thread);
  NOT_LP64(__ get_thread(thread1));
  const Address do_not_unlock_if_synchronized(thread1,
        in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
  __ movbool(do_not_unlock_if_synchronized, true);

  // increment invocation count & check for overflow
  Label invocation_counter_overflow;
  if (inc_counter) {
    generate_counter_incr(&invocation_counter_overflow, NULL, NULL);
  }

  Label continue_after_compile;
  __ bind(continue_after_compile);

  bang_stack_shadow_pages(true); 

  // reset the _do_not_unlock_if_synchronized flag
  NOT_LP64(__ get_thread(thread1));
  __ movbool(do_not_unlock_if_synchronized, false);


  // work registers
  const Register method = rbx;
  const Register thread = NOT_LP64(rdi) LP64_ONLY(r15_thread);
  const Register t      = NOT_LP64(rcx) LP64_ONLY(r11);

  // allocate space for parameters
  __ get_method(method);
  __ movptr(t, Address(method, Method::const_offset()));
  __ load_unsigned_short(t, Address(t, ConstMethod::size_of_parameters_offset()));

  __ shll(t, Interpreter::logStackElementSize);

  __ subptr(rsp, t);
  __ subptr(rsp, frame::arg_reg_save_area_bytes); // windows
  __ andptr(rsp, -16); // must be 16 byte boundary (see amd64 ABI)


  // get signature handler
  {
    Label L;
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ testptr(t, t);
    __ jcc(Assembler::notZero, L);
    __ call_VM(noreg,
               CAST_FROM_FN_PTR(address,
                                InterpreterRuntime::prepare_native_call),
               method);
    __ get_method(method);
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ bind(L);
  }

  // call signature handler
  assert(InterpreterRuntime::SignatureHandlerGenerator::from() == rlocals,
         "adjust this code");
  assert(InterpreterRuntime::SignatureHandlerGenerator::to() == rsp,
         "adjust this code");
  assert(InterpreterRuntime::SignatureHandlerGenerator::temp() == NOT_LP64(t) LP64_ONLY(rscratch1),
         "adjust this code");

  // The generated handlers do not touch RBX (the method oop).
  // However, large signatures cannot be cached and are generated
  // each time here.  The slow-path generator can do a GC on return,
  // so we must reload it after the call.
  __ call(t);
  __ get_method(method);        // slow path can do a GC, reload RBX


  // result handler is in rax
  // set result handler
  __ movptr(Address(rbp,
                    (frame::interpreter_frame_result_handler_offset) * wordSize),
            rax);

  // pass mirror handle if static call
  {
    Label L;
    __ movl(t, Address(method, Method::access_flags_offset()));
    __ testl(t, JVM_ACC_STATIC);
    __ jcc(Assembler::zero, L);
    // get mirror
    __ load_mirror(t, method, rax);
    // copy mirror into activation frame
    __ movptr(Address(rbp, frame::interpreter_frame_oop_temp_offset * wordSize),
            t);
    // pass handle to mirror

    __ lea(c_rarg1,
           Address(rbp, frame::interpreter_frame_oop_temp_offset * wordSize));

    __ bind(L);
  }

  // get native function entry point
  {
    Label L;
    __ movptr(rax, Address(method, Method::native_function_offset()));
    ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry());
    __ cmpptr(rax, unsatisfied.addr());
    __ jcc(Assembler::notEqual, L);
    __ call_VM(noreg,
               CAST_FROM_FN_PTR(address,
                                InterpreterRuntime::prepare_native_call),
               method);
    __ get_method(method);
    __ movptr(rax, Address(method, Method::native_function_offset()));
    __ bind(L);
  }

  // pass JNIEnv
   __ lea(c_rarg0, Address(r15_thread, JavaThread::jni_environment_offset()));

   // It is enough that the pc() points into the right code
   // segment. It does not have to be the correct return pc.
   __ set_last_Java_frame(rsp, rbp, (address) __ pc());




  // Change state to native

  __ movl(Address(thread, JavaThread::thread_state_offset()),
          _thread_in_native);

  // Call the native method.
  __ call(rax);
  // 32: result potentially in rdx:rax or ST0
  // 64: result potentially in rax or xmm0

  // Verify or restore cpu control state after JNI call
  __ restore_cpu_control_state_after_jni();

  // NOTE: The order of these pushes is known to frame::interpreter_frame_result
  // in order to extract the result of a method call. If the order of these
  // pushes change or anything else is added to the stack then the code in
  // interpreter_frame_result must also change.

  __ push(dtos);

  __ push(ltos);

  // change thread state
  NOT_LP64(__ get_thread(thread));
  __ movl(Address(thread, JavaThread::thread_state_offset()),
          _thread_in_native_trans);

  if (os::is_MP()) {
    if (UseMembar) {
      // Force this write out before the read below
      __ membar(Assembler::Membar_mask_bits(
           Assembler::LoadLoad | Assembler::LoadStore |
           Assembler::StoreLoad | Assembler::StoreStore));
    } else {
      // Write serialization page so VM thread can do a pseudo remote membar.
      // We use the current thread pointer to calculate a thread specific
      // offset to write to within the page. This minimizes bus traffic
      // due to cache line collision.
      __ serialize_memory(thread, rcx);
    }
  }

  // check for safepoint operation in progress and/or pending suspend requests
  {
    Label Continue;
    Label slow_path;

    __ safepoint_poll(slow_path, r15_thread, rscratch1);

    __ cmpl(Address(thread, JavaThread::suspend_flags_offset()), 0);
    __ jcc(Assembler::equal, Continue);
    __ bind(slow_path);

    // Don't use call_VM as it will see a possible pending exception
    // and forward it and never return here preventing us from
    // clearing _last_native_pc down below.  Also can't use
    // call_VM_leaf either as it will check to see if r13 & r14 are
    // preserved and correspond to the bcp/locals pointers. So we do a
    // runtime call by hand.
    //

    __ mov(c_rarg0, r15_thread);
    __ mov(r12, rsp); // remember sp (can only use r12 if not using call_VM)
    __ subptr(rsp, frame::arg_reg_save_area_bytes); // windows
    __ andptr(rsp, -16); // align stack as required by ABI
    __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, JavaThread::check_special_condition_for_native_trans)));
    __ mov(rsp, r12); // restore sp
    __ reinit_heapbase();

    __ bind(Continue);
  }

  // change thread state
  __ movl(Address(thread, JavaThread::thread_state_offset()), _thread_in_Java);

  // reset_last_Java_frame
  __ reset_last_Java_frame(thread, true);

  if (CheckJNICalls) {
    // clear_pending_jni_exception_check
    __ movptr(Address(thread, JavaThread::pending_jni_exception_check_fn_offset()), NULL_WORD);
  }

  // reset handle block
  __ movptr(t, Address(thread, JavaThread::active_handles_offset()));
  __ movl(Address(t, JNIHandleBlock::top_offset_in_bytes()), (int32_t)NULL_WORD);

  // If result is an oop unbox and store it in frame where gc will see it
  // and result handler will pick it up
  {
    Label no_oop, not_weak, store_result;
    __ lea(t, ExternalAddress(AbstractInterpreter::result_handler(T_OBJECT)));
    __ cmpptr(t, Address(rbp, frame::interpreter_frame_result_handler_offset*wordSize));
    __ jcc(Assembler::notEqual, no_oop);
    // retrieve result
    __ pop(ltos);
    // Unbox oop result, e.g. JNIHandles::resolve value.
    __ resolve_jobject(rax /* value */,
                       thread /* thread */,
                       t /* tmp */);
    __ movptr(Address(rbp, frame::interpreter_frame_oop_temp_offset*wordSize), rax);
    // keep stack depth as expected by pushing oop which will eventually be discarded
    __ push(ltos);
    __ bind(no_oop);
  }



  // The method register is junk from after the thread_in_native transition
  // until here.  Also can't call_VM until the bcp has been
  // restored.  Need bcp for throwing exception below so get it now.
  __ get_method(method);

  // restore to have legal interpreter frame, i.e., bci == 0 <=> code_base()
  __ movptr(rbcp, Address(method, Method::const_offset()));   // get ConstMethod*
  __ lea(rbcp, Address(rbcp, ConstMethod::codes_offset()));    // get codebase



  // restore potential result in edx:eax, call result handler to
  // restore potential result in ST0 & handle result

  __ pop(ltos);
  LP64_ONLY( __ pop(dtos));

  __ movptr(t, Address(rbp,
                       (frame::interpreter_frame_result_handler_offset) * wordSize));
  __ call(t);

  // remove activation
  __ movptr(t, Address(rbp,
                       frame::interpreter_frame_sender_sp_offset *
                       wordSize)); // get sender sp
  __ leave();                                // remove frame anchor
  __ pop(rdi);                               // get return address
  __ mov(rsp, t);                            // set sp to sender sp
  __ jmp(rdi);

  if (inc_counter) {
    // Handle overflow of counter and compile method
    __ bind(invocation_counter_overflow);
    generate_counter_overflow(continue_after_compile);
  }

  return entry_point;
}

除了对栈进行布局之外还有几个关键的部分,我们在对栈布局进行介绍之前先来看看这几个部分。

首先是获取method对象的signature_handler和native_function字段,两个部分的逻辑都差不多:

  // get signature handler
  {
    Label L;
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ testptr(t, t);
    __ jcc(Assembler::notZero, L);
    __ call_VM(noreg,
               CAST_FROM_FN_PTR(address,
                                InterpreterRuntime::prepare_native_call),
               method);
    __ get_method(method);
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ bind(L);
  }

首先获取并测试signature_handler是否为NULL,如果是NULL则调用InterpreterRuntime::prepare_native_call

IRT_ENTRY(void, InterpreterRuntime::prepare_native_call(JavaThread* thread, Method* method))
  methodHandle m(thread, method);
  assert(m->is_native(), "sanity check");
  // lookup native function entry point if it doesn't exist
  bool in_base_library;
  if (!m->has_native_function()) {  // 查看native_function字段是否为空,如果为空则需要进行获取。
    NativeLookup::lookup(m, in_base_library, CHECK);
  }
  // make sure signature handler is installed
  SignatureHandlerLibrary::add(m);
  // The interpreter entry point checks the signature handler first,
  // before trying to fetch the native entry point and klass mirror.
  // We must set the signature handler last, so that multiple processors
  // preparing the same method will be sure to see non-null entry & mirror.
IRT_END

我们先来看看第一步NativeLookup::lookup,这个函数尝试为method填充其native_function字段,其最终会调用NativeLookup::lookup_base:

address NativeLookup::lookup_base(const methodHandle& method, bool& in_base_library, TRAPS) {
  address entry = NULL;
  ResourceMark rm(THREAD);

  entry = lookup_entry(method, in_base_library, THREAD);
  if (entry != NULL) return entry;

  // standard native method resolution has failed.  Check if there are any
  // JVM TI prefixes which have been applied to the native method name.
  entry = lookup_entry_prefixed(method, in_base_library, THREAD);
  if (entry != NULL) return entry;

  // Native function not found, throw UnsatisfiedLinkError
  THROW_MSG_0(vmSymbols::java_lang_UnsatisfiedLinkError(),
              method->name_and_sig_as_C_string());
}

由于我并不懂Jvmti,所以只看NativeLookup::lookup_entry,这个应该是能够满足绝大多数的情况了,这个函数会进入到NativeLookup::lookup_style,不过在这之前会按照约定生成函数名,生成short name的方式在上面已经提到过了,生成long name的方式也很简单,所以直接看NativeLookup::lookup_style

address NativeLookup::lookup_style(const methodHandle& method, char* pure_name, const char* long_name, int args_size, bool os_style, bool& in_base_library, TRAPS) {
  address entry;
  // Compute complete JNI name for style
  stringStream st;
  if (os_style) os::print_jni_name_prefix_on(&st, args_size);
  st.print_raw(pure_name);
  st.print_raw(long_name);
  if (os_style) os::print_jni_name_suffix_on(&st, args_size);
  char* jni_name = st.as_string();

  // If the loader is null we have a system class, so we attempt a lookup in
  // the native Java library. This takes care of any bootstrapping problems.
  // Note: It is critical for bootstrapping that Java_java_lang_ClassLoader_00024NativeLibrary_find
  // gets found the first time around - otherwise an infinite loop can occure. This is
  // another VM/library dependency
  Handle loader(THREAD, method->method_holder()->class_loader());
  if (loader.is_null()) {
    entry = lookup_special_native(jni_name);
    if (entry == NULL) {
       entry = (address) os::dll_lookup(os::native_java_library(), jni_name); // 尝试从libjava.so中寻找。
    }
    if (entry != NULL) {
      in_base_library = true;
      return entry;
    }
  }

  // Otherwise call static method findNative in ClassLoader
  Klass*   klass = SystemDictionary::ClassLoader_klass();
  Handle name_arg = java_lang_String::create_from_str(jni_name, CHECK_NULL);

  JavaValue result(T_LONG);
  JavaCalls::call_static(&result,
                         klass,
                         vmSymbols::findNative_name(),
                         vmSymbols::classloader_string_long_signature(),
                         // Arguments
                         loader,
                         name_arg,
                         CHECK_NULL);
  entry = (address) (intptr_t) result.get_jlong();

  if (entry == NULL) {
    // findNative didn't find it, if there are any agent libraries look in them
    AgentLibrary* agent;
    for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {
      entry = (address) os::dll_lookup(agent->os_lib(), jni_name);
      if (entry != NULL) {
        return entry;
      }
    }
  }

  return entry;
}

大致是首先尝试从基本库中寻找,如果失败则根据类加载器来寻找,还记得上面提到过的System.loadLibrary()么?这个方法在加载动态库的时候还会让加载了这个类的类加载器记录下它加载过的动态库,保存在NativeLibrary对象中,然后ClassLoader.findNative()查找自己加载过的动态库,看是否存在这个符号,如果存在则加载成功。ClassLoader.findNative()最终会调用如下的函数:

/*
 * Class:     java_lang_ClassLoader_NativeLibrary
 * Method:    findEntry
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_findEntry
  (JNIEnv *env, jobject this, jstring name)
{
    jlong handle;
    const char *cname;
    jlong res;

    if (!initIDs(env))
        return jlong_zero;

    handle = (*env)->GetLongField(env, this, handleID);
    cname = (*env)->GetStringUTFChars(env, name, 0);
    if (cname == 0)
        return jlong_zero;
    res = ptr_to_jlong(JVM_FindLibraryEntry(jlong_to_ptr(handle), cname));
    (*env)->ReleaseStringUTFChars(env, name, cname);
    return res;
}

这个函数通过JVM_FindLibraryEntry对handle指向的动态库进行了查询。

查找函数地址的过程大致就是这样,现在我们返回到InterpreterRuntime::prepare_native_call

IRT_ENTRY(void, InterpreterRuntime::prepare_native_call(JavaThread* thread, Method* method))
  methodHandle m(thread, method);
  assert(m->is_native(), "sanity check");
  // lookup native function entry point if it doesn't exist
  bool in_base_library;
  if (!m->has_native_function()) {  // 查看native_function字段是否为空,如果为空则需要进行获取。
    NativeLookup::lookup(m, in_base_library, CHECK);
  }
  // make sure signature handler is installed
  SignatureHandlerLibrary::add(m);
  // The interpreter entry point checks the signature handler first,
  // before trying to fetch the native entry point and klass mirror.
  // We must set the signature handler last, so that multiple processors
  // preparing the same method will be sure to see non-null entry & mirror.
IRT_END

它接着调用了SignatureHandlerLibrary::add,如果此时的signature_handler为NULL则会对其进行填充:

void SignatureHandlerLibrary::add(const methodHandle& method) {
    // 判断是否已经有了handler。
  if (method->signature_handler() == NULL) {
    // use slow signature handler if we can't do better
    int handler_index = -1;
    // check if we can use customized (fast) signature handler
      // 如果method参数的大小(就是的slot的大小,int为1,double为2)小于等于13则查看是否有缓存handler。
    if (UseFastSignatureHandlers && method->size_of_parameters() <= Fingerprinter::max_size_of_parameters) { // Fingerprinter::max_size_of_parameters = 13
      // use customized signature handler
      MutexLocker mu(SignatureHandlerLibrary_lock);
      // make sure data structure is initialized
      initialize(); // 查看SignatureHandlerLibrary是否初始化,这是个静态单例对象,用于存放handler和fingerprint的map。
        
        // 计算fingerprint的类`Fingerprinter`继承自`SignatureIterator`,
        //这个接口在`JNI_ArgumentPusher`也见到过,处理逻辑大致就是遍历签名,然后在遍历的时候做出对应的行为。
      // lookup method signature's fingerprint
      uint64_t fingerprint = Fingerprinter(method).fingerprint(); // 根据签名计算fingerprint(指纹)。
      // allow CPU dependant code to optimize the fingerprints for the fast handler
      fingerprint = InterpreterRuntime::normalize_fast_native_fingerprint(fingerprint); // 没有什么意义。
      handler_index = _fingerprints->find(fingerprint); // 听过fingerprint找到handler在_handlers中对应下标。
      // create handler if necessary
      if (handler_index < 0) { // 查找失败,之前没有缓存对应的handler。
        ResourceMark rm;
        ptrdiff_t align_offset = align_up(_buffer, CodeEntryAlignment) - (address)_buffer;
        CodeBuffer buffer((address)(_buffer + align_offset),
                          SignatureHandlerLibrary::buffer_size - align_offset);
        InterpreterRuntime::SignatureHandlerGenerator(method, &buffer).generate(fingerprint);
        // copy into code heap
        address handler = set_handler(&buffer);
        if (handler == NULL) {
          // use slow signature handler (without memorizing it in the fingerprints)
        } else {
          // add handler to library
          _fingerprints->append(fingerprint);
          _handlers->append(handler);
          // set handler index
          assert(_fingerprints->length() == _handlers->length(), "sanity check");
          handler_index = _fingerprints->length() - 1;
        }
      }
      // Set handler under SignatureHandlerLibrary_lock
      if (handler_index < 0) {
        // use generic signature handler
        method->set_signature_handler(Interpreter::slow_signature_handler()); // 使用慢速的handler。
      } else {
        // set handler
        method->set_signature_handler(_handlers->at(handler_index)); // 通过index获取,然后设置。
      }
    } else {
      CHECK_UNHANDLED_OOPS_ONLY(Thread::current()->clear_unhandled_oops());
      // use generic signature handler
      method->set_signature_handler(Interpreter::slow_signature_handler()); // 超过了大小,使用慢速handler。
    }
  }

}

生成handler的SignatureHandlerGenerator继承了NativeSignatureIterator,而它又实现了SignatureIterator,所以又会对signature进行遍历,不过实际上只需要对fingerprint进行遍历就行了,通过对fingerprint进行遍历,生成了将参数转移到目标寄存器或者是栈中的代码,以对int的转移为例:

void InterpreterRuntime::SignatureHandlerGenerator::pass_int() {
  const Address src(from(), Interpreter::local_offset_in_bytes(offset()));


  // 根据当前已经处理了的_num_int_args来决定将参数从调用者(java方法)的操作数栈移动到native栈。
  switch (_num_int_args) { 
  case 0:
    __ movl(c_rarg1, src); // 从crag1开始,因为0是env*。
    _num_int_args++;
    break;
  case 1:
    __ movl(c_rarg2, src);
    _num_int_args++;
    break;
  case 2:
    __ movl(c_rarg3, src);
    _num_int_args++;
    break;
  case 3:
    __ movl(c_rarg4, src);
    _num_int_args++;
    break;
  case 4:
    __ movl(c_rarg5, src);
    _num_int_args++;
    break;
  default: // 如果超过了,则放在栈上传递。
    __ movl(rax, src);
    __ movl(Address(to(), _stack_offset), rax);
    _stack_offset += wordSize; // 栈的偏移对应增加。
    break;
  }
}

除此之外SignatureHandlerGenerator::generate还做了一些其他的事情:

void InterpreterRuntime::SignatureHandlerGenerator::generate(uint64_t fingerprint) {
  // generate code to handle arguments
  iterate(fingerprint);

    // 将result_handler作为了signature_handler的返回值进行返回,result_handler相对简单,只需要根据返回值类型即可获取预先生成好的result_handler例程。
  // return result handler
  __ lea(rax, ExternalAddress(Interpreter::result_handler(method()->result_type())));
  __ ret(0);

  __ flush();
}

生成result_handler的函数为TemplateInterpreterGenerator::generate_result_handler_for

address TemplateInterpreterGenerator::generate_result_handler_for(BasicType type) {
  address entry = __ pc();
    // 对返回值进行一些处理以满足java的要求。
  switch (type) {
  case T_BOOLEAN: __ c2bool(rax);            break; // 会对flag进行设置
  case T_CHAR   : __ movzwl(rax, rax);       break; // 0拓展
  case T_BYTE   : __ sign_extend_byte(rax);  break; // 符号拓展
  case T_SHORT  : __ sign_extend_short(rax); break; // 符号拓展
  case T_INT    : /* nothing to do */        break;
  case T_LONG   : /* nothing to do */        break;
  case T_VOID   : /* nothing to do */        break;
  case T_FLOAT  : /* nothing to do */        break;
  case T_DOUBLE : /* nothing to do */        break;
  case T_OBJECT :
    // retrieve result from frame
    __ movptr(rax, Address(rbp, frame::interpreter_frame_oop_temp_offset*wordSize)); // 将oop_temp的值转移到rax。
    // and verify it
    __ verify_oop(rax);
    break;
  default       : ShouldNotReachHere();
  }
  __ ret(0);                                   // return from result handler
  return entry;
}

为了更加好地理解生成的signature_handler和result_handler代码,需要回到generate_native_entry中,删除了其中一些代码:

// Interpreter stub for calling a native method. (asm interpreter)
// This sets up a somewhat different looking stack for calling the
// native method than the typical interpreter frame setup.
address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) {

  // rbx: Method*
  // rbcp: 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(rcx, ConstMethod::
                                        size_of_parameters_offset());


  // get parameter size (always needed)
  __ movptr(rcx, constMethod);
  __ load_unsigned_short(rcx, size_of_parameters);

  // native calls don't need the stack size check since they have no
  // expression stack and the arguments are already on the stack and
  // we only add a handful of words to the stack

  // rbx: Method*
  // rcx: size of parameters
  // rbcp: sender sp
  __ pop(rax);                                       // get return address

  // for natives the size of locals is zero

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

  // add 2 zero-initialized slots for native calls
  // initialize result_handler slot
  __ push((int) NULL_WORD);
  // slot for oop temp
  // (static native method holder mirror/jni oop result)
  __ push((int) NULL_WORD);

  // initialize fixed part of activation frame
  generate_fixed_frame(true);  // 这一步之前和调用java方法是一致的。
  // stackframe graph 1

  // 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.

  const Register thread1 = NOT_LP64(rax) LP64_ONLY(r15_thread);
  const Address do_not_unlock_if_synchronized(thread1,
        in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
  __ movbool(do_not_unlock_if_synchronized, true);


  Label continue_after_compile;
  __ bind(continue_after_compile);

  bang_stack_shadow_pages(true); // 涉及到栈溢出检测和nativa栈空间分配,暂时跳过,到栈内存管理中继续。

  // reset the _do_not_unlock_if_synchronized flag
  NOT_LP64(__ get_thread(thread1));
  __ movbool(do_not_unlock_if_synchronized, false);


  // work registers
  const Register method = rbx;
  const Register thread = NOT_LP64(rdi) LP64_ONLY(r15_thread);
  const Register t      = NOT_LP64(rcx) LP64_ONLY(r11);

  // allocate space for parameters
  __ get_method(method);
  __ movptr(t, Address(method, Method::const_offset()));
  __ load_unsigned_short(t, Address(t, ConstMethod::size_of_parameters_offset()));

  __ shll(t, Interpreter::logStackElementSize);

  __ subptr(rsp, t);  // 给参数准备栈空间,但是未必会全部用上。
  __ subptr(rsp, frame::arg_reg_save_area_bytes); // windows 非windows下为0。
  __ andptr(rsp, -16); // must be 16 byte boundary (see amd64 ABI)
  // stackframe graph 2

  // get signature handler
  {
    Label L;
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ testptr(t, t);
    __ jcc(Assembler::notZero, L);
    __ call_VM(noreg,
               CAST_FROM_FN_PTR(address,
                                InterpreterRuntime::prepare_native_call),
               method);
    __ get_method(method);
    __ movptr(t, Address(method, Method::signature_handler_offset()));
    __ bind(L);
  }

  // call signature handler
  assert(InterpreterRuntime::SignatureHandlerGenerator::from() == rlocals,
         "adjust this code");
  assert(InterpreterRuntime::SignatureHandlerGenerator::to() == rsp,
         "adjust this code");
  assert(InterpreterRuntime::SignatureHandlerGenerator::temp() == NOT_LP64(t) LP64_ONLY(rscratch1),
         "adjust this code");

  // The generated handlers do not touch RBX (the method oop).
  // However, large signatures cannot be cached and are generated
  // each time here.  The slow-path generator can do a GC on return,
  // so we must reload it after the call.
  __ call(t);  // 执行signature_handler。
  __ get_method(method);        // slow path can do a GC, reload RBX


  // result handler is in rax
  // set result handler
  __ movptr(Address(rbp,
                    (frame::interpreter_frame_result_handler_offset) * wordSize),
            rax); // 将result_handler保存在栈上。

  // pass mirror handle if static call
  {
    Label L;
    __ movl(t, Address(method, Method::access_flags_offset()));
    __ testl(t, JVM_ACC_STATIC);
    __ jcc(Assembler::zero, L);
    // get mirror
    __ load_mirror(t, method, rax);
    // copy mirror into activation frame
    __ movptr(Address(rbp, frame::interpreter_frame_oop_temp_offset * wordSize),
            t);
    // pass handle to mirror

    __ lea(c_rarg1,
           Address(rbp, frame::interpreter_frame_oop_temp_offset * wordSize)); // 放入第1个位置。

    __ bind(L);
  }

  // get native function entry point
  {
    Label L;
    __ movptr(rax, Address(method, Method::native_function_offset()));
    ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry());
    __ cmpptr(rax, unsatisfied.addr());
    __ jcc(Assembler::notEqual, L);
    __ call_VM(noreg,
               CAST_FROM_FN_PTR(address,
                                InterpreterRuntime::prepare_native_call),
               method);
    __ get_method(method);
    __ movptr(rax, Address(method, Method::native_function_offset()));
    __ bind(L);
  }

   // pass JNIEnv
   __ lea(c_rarg0, Address(r15_thread, JavaThread::jni_environment_offset())); // 第0个参数。

   // It is enough that the pc() points into the right code
   // segment. It does not have to be the correct return pc.
   __ set_last_Java_frame(rsp, rbp, (address) __ pc()); // 使得可以进行栈遍历。


  // Change state to native

  __ movl(Address(thread, JavaThread::thread_state_offset()),
          _thread_in_native);

  // Call the native method.
  __ call(rax); // 正式进入native方法。
  // 32: result potentially in rdx:rax or ST0
  // 64: result potentially in rax or xmm0

  // Verify or restore cpu control state after JNI call
  __ restore_cpu_control_state_after_jni();

  // NOTE: The order of these pushes is known to frame::interpreter_frame_result
  // in order to extract the result of a method call. If the order of these
  // pushes change or anything else is added to the stack then the code in
  // interpreter_frame_result must also change.

  __ push(dtos); // 和jvmti有关,可以不理会。

  __ push(ltos);

  // change thread state
  NOT_LP64(__ get_thread(thread));
  __ movl(Address(thread, JavaThread::thread_state_offset()),
          _thread_in_native_trans);

  if (os::is_MP()) {
    if (UseMembar) {
      // Force this write out before the read below
      __ membar(Assembler::Membar_mask_bits(
           Assembler::LoadLoad | Assembler::LoadStore |
           Assembler::StoreLoad | Assembler::StoreStore)); // 使得上面的状态切换(写入)能够被其他线程的读取发现。
    } else {
      // Write serialization page so VM thread can do a pseudo remote membar.
      // We use the current thread pointer to calculate a thread specific
      // offset to write to within the page. This minimizes bus traffic
      // due to cache line collision.
      __ serialize_memory(thread, rcx);
    }
  }


  // change thread state
  __ movl(Address(thread, JavaThread::thread_state_offset()), _thread_in_Java);

  // reset_last_Java_frame
  __ reset_last_Java_frame(thread, true);

  if (CheckJNICalls) {
    // clear_pending_jni_exception_check
    __ movptr(Address(thread, JavaThread::pending_jni_exception_check_fn_offset()), NULL_WORD);
  }

  // reset handle block
  __ movptr(t, Address(thread, JavaThread::active_handles_offset()));
  __ movl(Address(t, JNIHandleBlock::top_offset_in_bytes()), (int32_t)NULL_WORD);

  // If result is an oop unbox and store it in frame where gc will see it
  // and result handler will pick it up
  { // 和jni的引用有关。
    Label no_oop, not_weak, store_result;
    __ lea(t, ExternalAddress(AbstractInterpreter::result_handler(T_OBJECT)));
    __ cmpptr(t, Address(rbp, frame::interpreter_frame_result_handler_offset*wordSize));
    __ jcc(Assembler::notEqual, no_oop);
    // retrieve result
    __ pop(ltos);
    // Unbox oop result, e.g. JNIHandles::resolve value.
    __ resolve_jobject(rax /* value */,
                       thread /* thread */,
                       t /* tmp */);
    __ movptr(Address(rbp, frame::interpreter_frame_oop_temp_offset*wordSize), rax);
    // keep stack depth as expected by pushing oop which will eventually be discarded
    __ push(ltos);
    __ bind(no_oop);
  }



  // The method register is junk from after the thread_in_native transition
  // until here.  Also can't call_VM until the bcp has been
  // restored.  Need bcp for throwing exception below so get it now.
  __ get_method(method);

  // restore to have legal interpreter frame, i.e., bci == 0 <=> code_base()
  __ movptr(rbcp, Address(method, Method::const_offset()));   // get ConstMethod*
  __ lea(rbcp, Address(rbcp, ConstMethod::codes_offset()));    // get codebase



  // restore potential result in edx:eax, call result handler to
  // restore potential result in ST0 & handle result

  __ pop(ltos);
  LP64_ONLY( __ pop(dtos));

  __ movptr(t, Address(rbp,
                       (frame::interpreter_frame_result_handler_offset) * wordSize));
  __ call(t); // 调用result_handler。

  // remove activation
  __ movptr(t, Address(rbp,
                       frame::interpreter_frame_sender_sp_offset *
                       wordSize)); // get sender sp
  __ leave();                                // remove frame anchor
  __ pop(rdi);                               // get return address
  __ mov(rsp, t);                            // set sp to sender sp
  __ jmp(rdi); // 跳入到invoke_return_entry。

  return entry_point;
}

对应的栈布局:

stackframe graph 1

stackframe-native-1.drawio

stackframe graph 2

stackframe-native-2.drawio

rsp是signature_handler中的to,而rlocals则是from,需要注意的处理的顺序是从左到右,也就是在栈中的参数越是栈顶就越是靠左的,这一点也是符合了c/c++参数进栈顺序的:

arg-pushin.drawio

[1] 第46篇-signature_handler与result_handler

posted @ 2022-09-21 22:51  aana  阅读(322)  评论(0编辑  收藏  举报