查找native方法的本地實現(xiàn)函數(shù)native_function詳解
在之前介紹為native方法設置解釋執(zhí)行的入口時講到過Method實例的內(nèi)存布局,如下:

對于第1個slot來說,如果是native方法,其對應的本地函數(shù)的實現(xiàn)會放到Method實例的native_function這個slot中,將本地函數(shù)放到這個slot就是registerNative()函數(shù)要完成的。
在前面介紹為native方法生成解釋執(zhí)行入口時介紹過,當判斷出Method::native_function還沒有值時,會調(diào)用InterpreterRuntime::prepare_native_call()函數(shù)為Method::native_function賦值。
InterpreterRuntime::prepare_native_call()函數(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還沒有值,需要調(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ù)會先調(diào)用Method::has_native_function()函數(shù)檢查之前是否已經(jīng)在Method實例里記錄下了本地函數(shù)的入口地址。如果已經(jīng)記錄了的話,那么可能是JNI庫在JNI_OnLoad()函數(shù)執(zhí)行的時候調(diào)用了RegisterNatives()函數(shù)注冊了函數(shù)地址信息,也有可能不是第一次調(diào)用該native方法,之前已經(jīng)完成了查找記錄的過程。
我們在之前介紹JavaVM和JNIEnv時舉過一個使用RegisterNatives()函數(shù)注冊函數(shù)地址的小實例,如下:
static JNINativeMethod method = { // 本地方法描述
"getName", // Java方法名
"(I)Ljava/lang/String;", // Java方法簽名
(void *) getName // 綁定到對應的本地函數(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的本地實現(xiàn)函數(shù)為getName,通過RegisterNatives()函數(shù)確定這種映射關系。RegisterNatives()函數(shù)會調(diào)用JNI函數(shù)jni_RegisterNatives(),在jni_RegisterNatives()函數(shù)中調(diào)用register_native()函數(shù),register_native()函數(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方法時就可執(zhí)行Method::native_function函數(shù)了。
如果沒有在Method::native_function中記錄下函數(shù)地址,需要調(diào)用NativeLookup::lookup()函數(shù)來尋找native方法真正的目標在什么地方,然后把它記在Method實例里。調(diào)用NativeLookup::lookup()函數(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實例中。調(diào)用的lookup_base()函數(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ù)的實現(xiàn)如下 :
address NativeLookup::lookup_entry(
methodHandle method,
bool& in_base_library,
TRAPS
) {
address entry = NULL;
// in_base_library是引用傳遞
in_base_library = false;
// 構造出符合JNI規(guī)范的函數(shù)名
char* pure_name = pure_jni_name(method);
// 計算實參的參數(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,當為NULL時,表示沒有查找到對應的本地函數(shù)實現(xiàn)
return entry;
}
如上函數(shù)通過NativeLookup::pure_jni_name()函數(shù)來構造出符合JNI規(guī)范的函數(shù)名,然后通過NativeLookup::lookup_style()函數(shù)在查找路徑中能夠找到的所有動態(tài)鏈接庫里去找這個名字對應的地址。我們可以看到,函數(shù)的名稱有許多種可能,所以在查找不到對應的本地函數(shù)時,會多次調(diào)用NativeLookup::lookup_style()函數(shù)查找,如果最后沒有查到,則返回NULL。
其實對linux來說,如果第1次和第3次的查找邏輯一樣,第2次和第4次的查找邏輯一樣,所以我們只看第1次和第2次的查找邏輯即可。
(1)第1次查找
第一次查找時,調(diào)用的pure_jni_name()函數(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();
}
拼接出來的函數(shù)名稱是“Java_Java程序的package路徑_函數(shù)名”。
(2)第2次查找
如果有重載的native方法,那么按第1次查找時生成的函數(shù)名稱是無法查找到的,還需要在生成的函數(shù)名稱中加上參數(shù)相關信息,這樣才能區(qū)分出2個重載的native方法對應的本地函數(shù)的不同。
調(diào)用NativeLookup::long_jni_name(函數(shù)生成帶有參數(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ù)的實現(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);
}
}
}
我們舉個例子,如下:
public class TestJNIName {
public native void get();
public native void get(Object a,int b);
}
通過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);
可以看到在方法名稱后會添加雙下劃線,然后就是按照一定的規(guī)則拼接參數(shù)類型了。
第1次和第2次都會調(diào)用NativeLookup::lookup_style()函數(shù)查找本地函數(shù)。NativeLookup::lookup_style()函數(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());
// 當loader為NULL時,表示method所屬的類是通過系統(tǒng)類加載器加載的
if (loader.is_null()) {
// 如果是查找registerNatives()函數(shù),則直接返回實現(xiàn)函數(shù)的地址
entry = lookup_special_native(jni_name);
if (entry == NULL) {
// 查找本地動態(tài)鏈接庫,Linux下則是libjava.so
void* tmp = os::native_java_library();
// 找到本地動態(tài)鏈接庫,調(diào)用os::dll_lookup查找符號表
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個參數(shù)
name_arg, // 為findNative()傳遞的第2個參數(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()) {
// 找到本地動態(tài)鏈接庫,調(diào)用os::dll_lookup查找符號表
entry = (address) os::dll_lookup(agent->os_lib(), jni_name);
if (entry != NULL) {
return entry;
}
}
}
return entry;
}
根據(jù)如上函數(shù)的實現(xiàn),我們可以從3個地方來查找動態(tài)鏈接庫,找到動態(tài)鏈接庫后就可以調(diào)用os::dll_lookup()函數(shù)查找指定名稱的本地函數(shù)了。
(1)如果native方法所屬的類是系統(tǒng)類加載器加載的,那么系統(tǒng)類加載器中的native方法的本地函數(shù)實現(xiàn)一般會在libjava.so中。
(2)如果在libjava.so中沒有找到,則調(diào)用java.lang.ClassLoader.findNative()方法進行查找。調(diào)用java.lang.ClassLoader.findNative()方法能夠查找到用戶自己創(chuàng)建出的動態(tài)鏈接庫,如我們編寫native方法時,通常會通過System.load()或System.loadLibrary()方法加載動態(tài)鏈接庫,這2個方法最終會調(diào)用到ClassLoader.loadLibrary()方法將相關的動態(tài)鏈接庫保存下來供findNative()方法查找使用;?
(3)如果步驟1和步驟2都沒有找到,則從加載的代理庫中查找,如我們在虛擬機啟動時配置的-agentlib或attach到目標進程后發(fā)送load命令加載的動態(tài)鏈接庫都有可以包含本地函數(shù)的實現(xiàn)。
通過navie方法找對應的本地函數(shù)的實現(xiàn)過程如下圖所示。

到此這篇關于查找native方法的本地實現(xiàn)函數(shù)native_function的文章就介紹到這了,更多相關native方法native_function函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Maven?項目用Assembly打包可執(zhí)行jar包的方法
這篇文章主要介紹了Maven?項目用Assembly打包可執(zhí)行jar包的方法,該方法只可打包非spring項目的可執(zhí)行jar包,需要的朋友可以參考下2023-03-03
SpringBoot整合Redis哨兵模式的實現(xiàn)示例
Redis哨兵模式是Redis高可用方案的一種實現(xiàn)方式,通過哨兵來自動實現(xiàn)故障轉移,從而保證高可用,本文主要介紹了SpringBoot整合Redis哨兵模式的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2024-02-02
java List循環(huán)與Map循環(huán)的總結
這篇文章主要介紹了java List循環(huán)與Map循環(huán)的總結的相關資料,并附代碼實例,幫助大家學習理解,需要的朋友可以參考下2016-11-11
Request的包裝類HttpServletRequestWrapper的使用說明
這篇文章主要介紹了Request的包裝類HttpServletRequestWrapper的使用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
springboot利用AOP完成日志統(tǒng)計的詳細步驟
項目用到了過濾器,可能有的人會不理解,之所以用過濾器是因為想要在日志記錄post請求的json數(shù)據(jù)。本文重點給大家介紹springboot利用AOP完成日志統(tǒng)計的詳細步驟,感興趣的朋友跟隨小編一起看看吧2021-12-12

