欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

查找native方法的本地實(shí)現(xiàn)函數(shù)native_function詳解

 更新時(shí)間:2021年12月16日 09:28:42   作者:鳩摩(馬智)  
JDK開(kāi)放給用戶的源碼中隨處可見(jiàn)Native方法,被Native關(guān)鍵字聲明的方法說(shuō)明該方法不是以Java語(yǔ)言實(shí)現(xiàn)的,而是以本地語(yǔ)言實(shí)現(xiàn)的,Java可以直接拿來(lái)用。這里介紹下查找native方法的本地實(shí)現(xiàn)函數(shù)native_function,感興趣的朋友跟隨小編一起看看吧

在之前介紹為native方法設(shè)置解釋執(zhí)行的入口時(shí)講到過(guò)Method實(shí)例的內(nèi)存布局,如下:

對(duì)于第1個(gè)slot來(lái)說(shuō),如果是native方法,其對(duì)應(yīng)的本地函數(shù)的實(shí)現(xiàn)會(huì)放到Method實(shí)例的native_function這個(gè)slot中,將本地函數(shù)放到這個(gè)slot就是registerNative()函數(shù)要完成的。

在前面介紹為native方法生成解釋執(zhí)行入口時(shí)介紹過(guò),當(dāng)判斷出Method::native_function還沒(méi)有值時(shí),會(huì)調(diào)用InterpreterRuntime::prepare_native_call()函數(shù)為Method::native_function賦值。

InterpreterRuntime::prepare_native_call()函數(shù)的實(shí)現(xiàn)如下:

IRT_ENTRY(void, InterpreterRuntime::prepare_native_call(
    JavaThread* thread,
    Method*     method
))
  methodHandle m(thread, method);
  bool in_base_library;
 
  // 如果Method::native_function還沒(méi)有值,需要調(diào)用NativeLookup::lookup()函數(shù)
  if (!m->has_native_function()) {
    NativeLookup::lookup(m, in_base_library, CHECK);
  }
 
  // 保證Method::signature_handler有值
  SignatureHandlerLibrary::add(m);
IRT_END

如上函數(shù)會(huì)先調(diào)用Method::has_native_function()函數(shù)檢查之前是否已經(jīng)在Method實(shí)例里記錄下了本地函數(shù)的入口地址。如果已經(jīng)記錄了的話,那么可能是JNI庫(kù)在JNI_OnLoad()函數(shù)執(zhí)行的時(shí)候調(diào)用了RegisterNatives()函數(shù)注冊(cè)了函數(shù)地址信息,也有可能不是第一次調(diào)用該native方法,之前已經(jīng)完成了查找記錄的過(guò)程。

我們?cè)谥敖榻BJavaVM和JNIEnv時(shí)舉過(guò)一個(gè)使用RegisterNatives()函數(shù)注冊(cè)函數(shù)地址的小實(shí)例,如下:

static JNINativeMethod method = { // 本地方法描述
        "getName",                // Java方法名
        "(I)Ljava/lang/String;",  // Java方法簽名
        (void *) getName          // 綁定到對(duì)應(yīng)的本地函數(shù)
};
  
static bool  bindNative(JNIEnv *env) {
    jclass clazz;
    clazz = env->FindClass(CLASS_NAME);
    if (clazz == NULL) {
        return false;
    }
    return env->RegisterNatives(clazz, &method, 1) == 0;
}

native方法getName的本地實(shí)現(xiàn)函數(shù)為getName,通過(guò)RegisterNatives()函數(shù)確定這種映射關(guān)系。RegisterNatives()函數(shù)會(huì)調(diào)用JNI函數(shù)jni_RegisterNatives(),在jni_RegisterNatives()函數(shù)中調(diào)用register_native()函數(shù),register_native()函數(shù)的實(shí)現(xiàn)如下:

static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {
  Method* method = k()->lookup_method(name, signature);
  // ...
  if (entry != NULL) {
    method->set_native_function(entry,Method::native_bind_event_is_interesting);
  } else {
    method->clear_native_function();
  }
  return true;
}

可以看到,將本地函數(shù)getName()的地址保存到了Method::native_function中,這樣在執(zhí)行native方法時(shí)就可執(zhí)行Method::native_function函數(shù)了。

如果沒(méi)有在Method::native_function中記錄下函數(shù)地址,需要調(diào)用NativeLookup::lookup()函數(shù)來(lái)尋找native方法真正的目標(biāo)在什么地方,然后把它記在Method實(shí)例里。調(diào)用NativeLookup::lookup()函數(shù)查找本地函數(shù),實(shí)現(xiàn)如下:

源代碼位置:openjdk/hotspot/share/vm/prims/nativeLookup.cpp
address NativeLookup::lookup(
 methodHandle  method,
 bool&         in_base_library,
 TRAPS
) {
  if (!method->has_native_function()) {
    address entry = lookup_base(method, in_base_library, CHECK_NULL);
    method->set_native_function(entry,Method::native_bind_event_is_interesting);
  }
  return method->native_function();
}

調(diào)用lookup_base()函數(shù)獲取native_function,然后調(diào)用set_native_function()函數(shù)將native_function保存到Method實(shí)例中。調(diào)用的lookup_base()函數(shù)的實(shí)現(xiàn)如下:

address NativeLookup::lookup_base(
 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;
  // ...
}

如上函數(shù)調(diào)用的lookup_entry()函數(shù)的實(shí)現(xiàn)如下 :

address NativeLookup::lookup_entry(
 methodHandle  method,
 bool&         in_base_library,
 TRAPS
) {
  address entry = NULL;
  // in_base_library是引用傳遞
  in_base_library = false; 
  // 構(gòu)造出符合JNI規(guī)范的函數(shù)名
  char* pure_name = pure_jni_name(method);
 
  // 計(jì)算實(shí)參的參數(shù)數(shù)量
  int args_size = 1                             // JNIEnv
                + (method->is_static() ? 1 : 0) // class for static methods
                + method->size_of_parameters(); // actual parameters
 
 
  // 1) Try JNI short style
  entry = lookup_style(method, pure_name, "",args_size, true,  in_base_library, CHECK_NULL);
  if (entry != NULL){
      return entry;
  }
  // Compute long name
  char* long_name = long_jni_name(method);
 
  // 2) Try JNI long style
  entry = lookup_style(method, pure_name, long_name, args_size, true,  in_base_library, CHECK_NULL);
  if (entry != NULL){
      return entry;
  }
  // 3) Try JNI short style without os prefix/suffix
  entry = lookup_style(method, pure_name, "",args_size, false, in_base_library, CHECK_NULL);
  if (entry != NULL){
      return entry;
  }
  // 4) Try JNI long style without os prefix/suffix
  entry = lookup_style(method, pure_name, long_name, args_size, false, in_base_library, CHECK_NULL);
 
 // entry可能為NULL,當(dāng)為NULL時(shí),表示沒(méi)有查找到對(duì)應(yīng)的本地函數(shù)實(shí)現(xiàn)
  return entry;
}

如上函數(shù)通過(guò)NativeLookup::pure_jni_name()函數(shù)來(lái)構(gòu)造出符合JNI規(guī)范的函數(shù)名,然后通過(guò)NativeLookup::lookup_style()函數(shù)在查找路徑中能夠找到的所有動(dòng)態(tài)鏈接庫(kù)里去找這個(gè)名字對(duì)應(yīng)的地址。我們可以看到,函數(shù)的名稱有許多種可能,所以在查找不到對(duì)應(yīng)的本地函數(shù)時(shí),會(huì)多次調(diào)用NativeLookup::lookup_style()函數(shù)查找,如果最后沒(méi)有查到,則返回NULL。

其實(shí)對(duì)linux來(lái)說(shuō),如果第1次和第3次的查找邏輯一樣,第2次和第4次的查找邏輯一樣,所以我們只看第1次和第2次的查找邏輯即可。

(1)第1次查找

第一次查找時(shí),調(diào)用的pure_jni_name()函數(shù)的實(shí)現(xiàn)如下:

char* NativeLookup::pure_jni_name(methodHandle method) {
  stringStream st;
  // 前綴
  st.print("Java_");
  // 類名稱
  mangle_name_on(&st, method->klass_name());
  st.print("_");
  // 方法名稱
  mangle_name_on(&st, method->name());
  return st.as_string();
}

拼接出來(lái)的函數(shù)名稱是“Java_Java程序的package路徑_函數(shù)名”。

(2)第2次查找

如果有重載的native方法,那么按第1次查找時(shí)生成的函數(shù)名稱是無(wú)法查找到的,還需要在生成的函數(shù)名稱中加上參數(shù)相關(guān)信息,這樣才能區(qū)分出2個(gè)重載的native方法對(duì)應(yīng)的本地函數(shù)的不同。

調(diào)用NativeLookup::long_jni_name(函數(shù)生成帶有參數(shù)相關(guān)信息的函數(shù)名稱,函數(shù)的實(shí)現(xiàn)如下:

char* NativeLookup::long_jni_name(methodHandle method) {
  // Signature ignore the wrapping parenteses and the trailing return type
  stringStream st;
  Symbol* signature = method->signature();
  st.print("__");
  // find ')'
  int end;
  for (end = 0; end < signature->utf8_length() && signature->byte_at(end) != ')'; end++);
  // skip first '('
  mangle_name_on(&st, signature, 1, end);
  return st.as_string();
}

調(diào)用的mangle_name_on()函數(shù)的實(shí)現(xiàn)如下:

static void mangle_name_on(outputStream* st, Symbol* name, int begin, int end) {
  char* bytes = (char*)name->bytes() + begin;
  char* end_bytes = (char*)name->bytes() + end;
  while (bytes < end_bytes) {
    jchar c;
    bytes = UTF8::next(bytes, &c);
    if (c <= 0x7f && isalnum(c)) {
      st->put((char) c);
    } else {
           if (c == '_') st->print("_1");
      else if (c == '/') st->print("_");
      else if (c == ';') st->print("_2");
      else if (c == '[') st->print("_3");
      else               st->print("_%.5x", c);
    }
  }
}

我們舉個(gè)例子,如下:

public class TestJNIName {
    public native void get();
    public native void get(Object a,int b);
}

通過(guò)javah生成的TestJNIName.h文件的主要內(nèi)容如下:

JNIEXPORT void JNICALL  Java_TestJNIName_get__
  (JNIEnv *, jobject);
 
JNIEXPORT void JNICALL  Java_TestJNIName_get__Ljava_lang_Object_2I
  (JNIEnv *, jobject, jobject, jint);

可以看到在方法名稱后會(huì)添加雙下劃線,然后就是按照一定的規(guī)則拼接參數(shù)類型了。

第1次和第2次都會(huì)調(diào)用NativeLookup::lookup_style()函數(shù)查找本地函數(shù)。NativeLookup::lookup_style()函數(shù)的實(shí)現(xiàn)如下:

address NativeLookup::lookup_style(
 methodHandle   method,
 char*          pure_name,
 const char*   long_name,
 int            args_size,
 bool           os_style,
 bool&          in_base_library,
 TRAPS
) {
  address entry;
  // 拼接pure_name和long_name
  stringStream st;
  st.print_raw(pure_name);
  st.print_raw(long_name);
  char* jni_name = st.as_string();
 
  Handle loader(THREAD, method->method_holder()->class_loader());
  // 當(dāng)loader為NULL時(shí),表示method所屬的類是通過(guò)系統(tǒng)類加載器加載的
  if (loader.is_null()) {
    // 如果是查找registerNatives()函數(shù),則直接返回實(shí)現(xiàn)函數(shù)的地址
    entry = lookup_special_native(jni_name);
    if (entry == NULL) {
       // 查找本地動(dòng)態(tài)鏈接庫(kù),Linux下則是libjava.so
       void* tmp = os::native_java_library();
       // 找到本地動(dòng)態(tài)鏈接庫(kù),調(diào)用os::dll_lookup查找符號(hào)表
       entry = (address) os::dll_lookup(tmp, jni_name);
    }
    if (entry != NULL) {
      in_base_library = true;
      return entry;
    }
  }
 
  // Otherwise call static method findNative in ClassLoader
  // 調(diào)用java.lang.ClassLoader中的findNative()方法查找
  KlassHandle   klass (THREAD, 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(),
                         loader,   // 為findNative()傳遞的第1個(gè)參數(shù)
                         name_arg, // 為findNative()傳遞的第2個(gè)參數(shù)
                         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()) {
      // 找到本地動(dòng)態(tài)鏈接庫(kù),調(diào)用os::dll_lookup查找符號(hào)表
      entry = (address) os::dll_lookup(agent->os_lib(), jni_name);
      if (entry != NULL) {
        return entry;
      }
    }
  }
 
  return entry;
}

根據(jù)如上函數(shù)的實(shí)現(xiàn),我們可以從3個(gè)地方來(lái)查找動(dòng)態(tài)鏈接庫(kù),找到動(dòng)態(tài)鏈接庫(kù)后就可以調(diào)用os::dll_lookup()函數(shù)查找指定名稱的本地函數(shù)了。

(1)如果native方法所屬的類是系統(tǒng)類加載器加載的,那么系統(tǒng)類加載器中的native方法的本地函數(shù)實(shí)現(xiàn)一般會(huì)在libjava.so中。

(2)如果在libjava.so中沒(méi)有找到,則調(diào)用java.lang.ClassLoader.findNative()方法進(jìn)行查找。調(diào)用java.lang.ClassLoader.findNative()方法能夠查找到用戶自己創(chuàng)建出的動(dòng)態(tài)鏈接庫(kù),如我們編寫(xiě)native方法時(shí),通常會(huì)通過(guò)System.load()或System.loadLibrary()方法加載動(dòng)態(tài)鏈接庫(kù),這2個(gè)方法最終會(huì)調(diào)用到ClassLoader.loadLibrary()方法將相關(guān)的動(dòng)態(tài)鏈接庫(kù)保存下來(lái)供findNative()方法查找使用;?

(3)如果步驟1和步驟2都沒(méi)有找到,則從加載的代理庫(kù)中查找,如我們?cè)谔摂M機(jī)啟動(dòng)時(shí)配置的-agentlib或attach到目標(biāo)進(jìn)程后發(fā)送load命令加載的動(dòng)態(tài)鏈接庫(kù)都有可以包含本地函數(shù)的實(shí)現(xiàn)。

通過(guò)navie方法找對(duì)應(yīng)的本地函數(shù)的實(shí)現(xiàn)過(guò)程如下圖所示。

到此這篇關(guān)于查找native方法的本地實(shí)現(xiàn)函數(shù)native_function的文章就介紹到這了,更多相關(guān)native方法native_function函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • Maven?項(xiàng)目用Assembly打包可執(zhí)行jar包的方法

    Maven?項(xiàng)目用Assembly打包可執(zhí)行jar包的方法

    這篇文章主要介紹了Maven?項(xiàng)目用Assembly打包可執(zhí)行jar包的方法,該方法只可打包非spring項(xiàng)目的可執(zhí)行jar包,需要的朋友可以參考下
    2023-03-03
  • Spring?Cloud?Eureka(全面解析)?大白話

    Spring?Cloud?Eureka(全面解析)?大白話

    這篇文章主要介紹了Spring?Cloud?Eureka(全面解析)?大白話,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringBoot整合Redis哨兵模式的實(shí)現(xiàn)示例

    SpringBoot整合Redis哨兵模式的實(shí)現(xiàn)示例

    Redis哨兵模式是Redis高可用方案的一種實(shí)現(xiàn)方式,通過(guò)哨兵來(lái)自動(dòng)實(shí)現(xiàn)故障轉(zhuǎn)移,從而保證高可用,本文主要介紹了SpringBoot整合Redis哨兵模式的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • Java語(yǔ)言打印九九乘法表

    Java語(yǔ)言打印九九乘法表

    這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言打印九九乘法表的相關(guān)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Sentinel整合Feign流程詳細(xì)講解

    Sentinel整合Feign流程詳細(xì)講解

    要想整合Feign,首先要了解Feign的使用以及執(zhí)行過(guò)程,然后看?Sentinel如何整合進(jìn)去,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Java貪吃蛇游戲完善版

    Java貪吃蛇游戲完善版

    這篇文章主要為大家詳細(xì)介紹了Java貪吃蛇游戲完善版,支持菜單操作,鍵盤(pán)監(jiān)聽(tīng),可加速,減速,統(tǒng)計(jì)得分等功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • java  List循環(huán)與Map循環(huán)的總結(jié)

    java List循環(huán)與Map循環(huán)的總結(jié)

    這篇文章主要介紹了java List循環(huán)與Map循環(huán)的總結(jié)的相關(guān)資料,并附代碼實(shí)例,幫助大家學(xué)習(xí)理解,需要的朋友可以參考下
    2016-11-11
  • 教你java面試時(shí)如何聊單例模式

    教你java面試時(shí)如何聊單例模式

    這篇文章主要給大家介紹了關(guān)于Java單例模式推薦的幾種模式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-06-06
  • Request的包裝類HttpServletRequestWrapper的使用說(shuō)明

    Request的包裝類HttpServletRequestWrapper的使用說(shuō)明

    這篇文章主要介紹了Request的包裝類HttpServletRequestWrapper的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • springboot利用AOP完成日志統(tǒng)計(jì)的詳細(xì)步驟

    springboot利用AOP完成日志統(tǒng)計(jì)的詳細(xì)步驟

    項(xiàng)目用到了過(guò)濾器,可能有的人會(huì)不理解,之所以用過(guò)濾器是因?yàn)橄胍谌罩居涗沺ost請(qǐng)求的json數(shù)據(jù)。本文重點(diǎn)給大家介紹springboot利用AOP完成日志統(tǒng)計(jì)的詳細(xì)步驟,感興趣的朋友跟隨小編一起看看吧
    2021-12-12

最新評(píng)論