我的编程空间,编程开发者的网络收藏夹
学习永远不晚

查找native方法的本地实现函数native_function详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

查找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方法找对应的本地函数的实现过程如下图所示。

到此这篇关于查找native方法的本地实现函数native_function的文章就介绍到这了,更多相关native方法native_function函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

查找native方法的本地实现函数native_function详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Golang实现IP地址转整数的方法详解

本文介绍了Go语言中将IP地址转换为整数的方法,包括使用net.ParseIP和net.IPv4函数解析IP地址,以及使用ToInt方法将其转换为整数。它强调了net.ParseIP可解析IPv4和IPv6地址,而net.IPv4专用于IPv4地址。最后,文章提醒读者注意转换后的整数表示IP地址的二进制表示,并提供了一个将"192.168.1.1"转换为整数的示例。
Golang实现IP地址转整数的方法详解
2024-04-02

实现.Net7下数据库定时检查的方法详解

在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,本文我们在 .Net 7 下进行一次简单的演示。感兴趣的可以了解一下
2022-12-20

Java模拟rank/over函数实现获取分组排名的方法详解

这篇文章主要为大家详细介绍了Java模拟rank()、over()函数获取分组排名的方法设计及实现,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
2023-05-15

C++ 成员函数详解:对象方法的底层实现与编译过程

c++++ 中的成员函数是附加在类中的对象方法,用于操作对象中的数据成员。编译过程包括:实例化:为每个成员函数创建函数指针,存储在对象中;调用机制:编译器自动插入类似于 result = ((_this)->*memberfunction(
C++ 成员函数详解:对象方法的底层实现与编译过程
2024-04-29

Android编程实现根据经纬度查询地址并对获取的json数据进行解析的方法

本文实例讲述了Android编程实现根据经纬度查询地址并对获取的json数据进行解析的方法。分享给大家供大家参考,具体如下: 第一步:根据指定的URL从google 服务器上获得包含地址的json格式的数据(其还提供xml格式的,但json
2022-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录