第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,拉你入虚拟机群交流
第45篇-查找native方法的本地实现函数native_function的更多相关文章
- 第44篇-为native方法设置解释执行入口
对于Java中的native方法来说,实际上调用的是C/C++实现的本地函数,由于可能会在Java解释执行过程中调用native方法,或在本地函数的实现过程中调用Java方法,所以当两者相互调用时,必 ...
- 认识理解Java中native方法(本地方法)
Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能. 可 ...
- JNI/NDK开发指南(二)——JVM查找java native方法的规则
通过第一篇文章,大家明白了调用native方法之前,首先要调用System.loadLibrary接口加载一个实现了native方法的动态库才能正常访问,否则就会抛出java.lang.Unsatis ...
- JVM本地方法栈及native方法
看到虚拟机栈和本地方法栈的区别的时候有点疑惑,因为本地方法栈为虚拟机的Native方法服务.以下转载一篇关于native方法的介绍: http://blog.csdn.net/wike163/arti ...
- 第47篇-解释执行的Java方法调用native方法小实例
举个小实例,如下: public class TestJNI { static { // 程序在加载时,自动加载libdiaoyong.so库 System.loadLibrary("dia ...
- JNI动态注册native方法及JNI数据使用
前言 或许你知道了jni的简单调用,其实不算什么百度谷歌一大把,虽然这些jni绝大多数情况下都不会让我们安卓工程师来弄,毕竟还是有点难,但是我们还是得打破砂锅知道为什么这样干吧,至少也让我们知道调用流 ...
- native方法
看到虚拟机栈和本地方法栈的区别的时候有点疑惑,因为本地方法栈为虚拟机的Native方法服务.以下转载一篇关于native方法的介绍: http://blog.csdn.net/wike163/arti ...
- 第39篇-Java通过JNI调用C/C++函数
在某些情况下,Java语言需要通过调用C/C++函数来实现某些功能,因为Java有时候对这些功能显的无能为力,如想使用X86_64 的 SIMD 指令提升一下业务方法中关键代码的性能,又或者想要获取某 ...
- 第48篇-native方法调用解释执行的Java方法
举一个native方法调用解释执行的Java方法的实例,如下: public class TestJNI { static { System.load("/media/mazhi/sourc ...
随机推荐
- C++内存管理剖析
C++内存管理 C++中有四种内存分配.释放方式: 最高级的是std::allocator,对应的释放方式是std::deallocate,可以自由设计来搭配任何容器:new/delete系列是C++ ...
- 基于linux与线程池实现文件管理
项目要求 1.基本 用线程池实现一个大文件夹的拷贝,大文件夹嵌套很多小文件:实现复制到指定文件夹的全部文件夹. 2.扩充功能 显示进度条:拷贝耗时统计:类似linux的tree,不能直接用system ...
- [luogu4484]最长上升子序列
标算是状压dp+打表,前者时间复杂度为$o(n^{2}2^{n})$,并通过打表做到$o(1)$ 参考loj2265中关于杨表的相关知识,不难发现答案即$\frac{\sum_{a\vdash n}a ...
- CKAD认证中的部署教程
在上一章中,我们已经学会了使用 kubeadm 创建集群和加入新的节点,在本章中,将按照 CKAD 课程的方法重新部署一遍,实际上官方教程的内容不多,笔者写了两篇类似的部署方式,如果已经部署了 kub ...
- Windows系统及硬件信息读取
Windows桌面端开发常常会需要读取系统信息或硬件信息作为用户标识,比如用于确认该设配是否已经激活程序.也可以使用随机生成的UUID来作为唯一标识,但是如果重装系统或重装软件都有可能导致标识丢失,因 ...
- 【Lua】实现代码执行覆盖率统计工具
一.如何评估测试过程的测试情况? 很多时候完成功能测试后就会发布上线,甚至交叉和回归都没有足够的时间去执行,然后通过线上的补丁对遗漏的问题进行修复.如果可以在发布前了解本次测试过程所覆盖代码执行的比例 ...
- Codeforces 295D - Greg and Caves(dp)
题意: 给出一个 \(n \times m\) 的矩阵,需对其进行黑白染色,使得以下条件成立: 存在区间 \([l,r]\)(\(1\leq l\leq r\leq n\)),使得第 \(l,l+1, ...
- linux 网络配置管理
[1]网络配置基础 (1)用户既可以通过命令行的方式,也可以通过友好的图形界面,轻松完成网络配置. (2)实现Linux网络配置的惟一目标就是修改系统中众多的网络配置文件, 如/etc/interfa ...
- SQL-关联表查询(连表查询)
0.例如:select * from T1,T2 where T1.a=T2.a 1.连表查询 <=> join(inner join)内连接查询 数据源: Persion表: ...
- Redis的六种特性 场景
Redis的六种特性 l Strings l Hashs l Lists l Sets l Sorted Sets l Pub/Sub Redis各特性的应用场景 Strings Strings 数据 ...