第45篇-查找native方法的本地实现函数native_function

在之前介绍为native方法设置解释执行的入口时讲到过Method实例的内存布局,如下:
对于第1个slot来说,如果是native方法,其对应的本地函数的实现会放到Method实例的native_function这个slot中,将本地函数放到这个slot就是registerNative()函数要完成的。
在前面介绍为native方法生成解释执行入口时介绍过,当判断出Method::native_function还没有值时,会调用InterpreterRuntime::prepare_native_call()函数为Method::native_function赋值。
InterpreterRuntime::prepare_native_call()函数的实现如下:
?IRT_ENTRY(
void
, InterpreterRuntime::prepare_native_call(
JavaThread* thread,
Method* method
))
methodHandle m(thread, method);
bool
in_base_library;
// 如果Method::native_function还没有值,需要调用NativeLookup::lookup()函数
if
(!m->has_native_function()) {
NativeLookup::lookup(m, in_base_library, CHECK);
}
// 保证Method::signature_handler有值
SignatureHandlerLibrary::add(m);
IRT_END
如上函数会先调用Method::has_native_function()函数检查之前是否已经在Method实例里记录下了本地函数的入口地址。如果已经记录了的话,那么可能是JNI库在JNI_OnLoad()函数执行的时候调用了RegisterNatives()函数注册了函数地址信息,也有可能不是第一次调用该native方法,之前已经完成了查找记录的过程。
我们在之前介绍JavaVM和JNIEnv时举过一个使用RegisterNatives()函数注册函数地址的小实例,如下:
?static
JNINativeMethod method = {
// 本地方法描述
"getName"
,
// Java方法名
"(I)Ljava/lang/String;"
,
// Java方法签名
(
void
*) getName
// 绑定到对应的本地函数
};
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的本地实现函数为getName,通过RegisterNatives()函数确定这种映射关系。RegisterNatives()函数会调用JNI函数jni_RegisterNatives(),在jni_RegisterNatives()函数中调用register_native()函数,register_native()函数的实现如下:
?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
;
}
可以看到,将本地函数getName()的地址保存到了Method::native_function中,这样在执行native方法时就可执行Method::native_function函数了。
如果没有在Method::native_function中记录下函数地址,需要调用NativeLookup::lookup()函数来寻找native方法真正的目标在什么地方,然后把它记在Method实例里。调用NativeLookup::lookup()函数查找本地函数,实现如下:
?源代码位置: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();
}
调用lookup_base()函数获取native_function,然后调用set_native_function()函数将native_function保存到Method实例中。调用的lookup_base()函数的实现如下:
?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;
// ...
}
如上函数调用的lookup_entry()函数的实现如下 :
?address NativeLookup::lookup_entry(
methodHandle method,
bool
& in_base_library,
TRAPS
) {
address entry = NULL;
// in_base_library是引用传递
in_base_library =
false
;
// 构造出符合JNI规范的函数名
char
* pure_name = pure_jni_name(method);
// 计算实参的参数数量
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时,表示没有查找到对应的本地函数实现
return
entry;
}
如上函数通过NativeLookup::pure_jni_name()函数来构造出符合JNI规范的函数名,然后通过NativeLookup::lookup_style()函数在查找路径中能够找到的所有动态链接库里去找这个名字对应的地址。我们可以看到,函数的名称有许多种可能,所以在查找不到对应的本地函数时,会多次调用NativeLookup::lookup_style()函数查找,如果最后没有查到,则返回NULL。
其实对linux来说,如果第1次和第3次的查找逻辑一样,第2次和第4次的查找逻辑一样,所以我们只看第1次和第2次的查找逻辑即可。
(1)第1次查找
第一次查找时,调用的pure_jni_name()函数的实现如下:
?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();
}
拼接出来的函数名称是“Java_Java程序的package路径_函数名”。
(2)第2次查找
如果有重载的native方法,那么按第1次查找时生成的函数名称是无法查找到的,还需要在生成的函数名称中加上参数相关信息,这样才能区分出2个重载的native方法对应的本地函数的不同。
调用NativeLookup::long_jni_name(函数生成带有参数相关信息的函数名称,函数的实现如下:
?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();
}
调用的mangle_name_on()函数的实现如下:
?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文件的主要内容如下:
?JNIEXPORT
void
JNICALL Java_TestJNIName_get__
(JNIEnv *, jobject);
JNIEXPORT
void
JNICALL Java_TestJNIName_get__Ljava_lang_Object_2I
(JNIEnv *, jobject, jobject, jint);
可以看到在方法名称后会添加双下划线,然后就是按照一定的规则拼接参数类型了。
第1次和第2次都会调用NativeLookup::lookup_style()函数查找本地函数。NativeLookup::lookup_style()函数的实现如下:
?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所属的类是通过系统类加载器加载的
if
(loader.is_null()) {
// 如果是查找registerNatives()函数,则直接返回实现函数的地址
entry = lookup_special_native(jni_name);
if
(entry == NULL) {
// 查找本地动态链接库,Linux下则是libjava.so
void
* tmp = os::native_java_library();
// 找到本地动态链接库,调用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
// 调用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个参数
name_arg,
// 为findNative()传递的第2个参数
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()) {
// 找到本地动态链接库,调用os::dll_lookup查找符号表
entry = (address) os::dll_lookup(agent->os_lib(), jni_name);
if
(entry != NULL) {
return
entry;
}
}
}
return
entry;
}
根据如上函数的实现,我们可以从3个地方来查找动态链接库,找到动态链接库后就可以调用os::dll_lookup()函数查找指定名称的本地函数了。
(1)如果native方法所属的类是系统类加载器加载的,那么系统类加载器中的native方法的本地函数实现一般会在libjava.so中。
(2)如果在libjava.so中没有找到,则调用java.lang.ClassLoader.findNative()方法进行查找。调用java.lang.ClassLoader.findNative()方法能够查找到用户自己创建出的动态链接库,如我们编写native方法时,通常会通过System.load()或System.loadLibrary()方法加载动态链接库,这2个方法最终会调用到ClassLoader.loadLibrary()方法将相关的动态链接库保存下来供findNative()方法查找使用;
(3)如果步骤1和步骤2都没有找到,则从加载的代理库中查找,如我们在虚拟机启动时配置的-agentlib或attach到目标进程后发送load命令加载的动态链接库都有可以包含本地函数的实现。
通过navie方法找对应的本地函数的实现过程如下图所示。
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流
相关内容
win10多任务按键怎么设置在底部详细方法
win10多任务按键怎么设置在底部详细方法,按键,任务,在使用电脑...
怎么设置电脑桌面|怎么设置电脑桌面壁纸
怎么设置电脑桌面|怎么设置电脑桌面壁纸,,怎么设置电脑桌面壁...
excel怎么用乘法函数
excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、...
固定路由器如何设置固定无线路由器设置教程
固定路由器如何设置固定无线路由器设置教程,,如今我们的智能手...
如何链接路由器设置TP链接无线路由器设置
如何链接路由器设置TP链接无线路由器设置,,不管是什么品牌的无...
icloud设置哪些该打开|icloud在设置哪里打开
icloud设置哪些该打开|icloud在设置哪里打开,,icloud设置哪些...
酷派手机恢复出厂设置|酷派手机恢复出厂设置后
酷派手机恢复出厂设置|酷派手机恢复出厂设置后上不了网,,酷派...
设置证书密码是什么|证书初始密码
设置证书密码是什么|证书初始密码,,设置证书密码是什么证书密...
设置里程碑|设置里程碑的方法有哪些
设置里程碑|设置里程碑的方法有哪些,,1. 设置里程碑的方法有哪...
计算机主板BIOS设置详细-BIOS知识
计算机主板BIOS设置详细-BIOS知识,,什么是电脑BIOS,一般电脑主...
金蝶k3入如何设置公式|金蝶K3中计量单位该如何
金蝶k3入如何设置公式|金蝶K3中计量单位该如何设置呢?,,1.金蝶...
苹果电脑麦克风设置|苹果电脑允许麦克风在哪里
苹果电脑麦克风设置|苹果电脑允许麦克风在哪里设置,,1. 苹果电...
笔记本如何设置Fn快捷键|笔记本如何设置fn快捷
笔记本如何设置Fn快捷键|笔记本如何设置fn快捷键,,笔记本如何...
快捷键查找是什么|电脑的快捷键是哪个键
快捷键查找是什么|电脑的快捷键是哪个键,,电脑的快捷键是哪个...
花呗怎么设置额度|花呗怎么设置额度调低
花呗怎么设置额度|花呗怎么设置额度调低,,1. 花呗怎么设置额度...