性能文章>Java层面调用native方法是怎么找到本地库中对应方法的?>

Java层面调用native方法是怎么找到本地库中对应方法的?原创

7月前
218502

问:在Java代码上调用了一个本地方法,比如调用某个对象的getClass()本地方法,那么在java层面调用开始到找到本地库中的如下方法?

JNIEXPORT jclass JNICALL  
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)  
{  
    if (this == NULL) {  
        JNU_ThrowNullPointerException(env, NULL);  
        return 0;  
    } else {  
        return (*env)->GetObjectClass(env, this);  
    }  
}  

这期间到底发什么了什么?怎么找到这个本地方法的,相关vm代码在哪里呢?


native lookup/invocation分好几步做

首先是VM启动阶段
InterpreterGenerator::generate_native_entry()生成native方法的解释器入口(下称native entry)。

类加载的时候,ClassFileParser看到某个方法的修饰符里有ACC_NATIVE就会在该方法对应的Method的AccessFlags里同样记录下JVM_ACC_NATIVE。这样该method->is_native()就会是true。

接下来到类的初始化阶段
instanceKlass::initialize()
-> instanceKlass::initialize_impl()
-> instanceKlass::link_class()
-> instanceKlass::link_class_impl()
-> instanceKlass::rewrite_class()
-> Rewriter::rewrite()
-> Rewriter::Rewriter()
-> methodOopDesc::link_method()
-> Interpreter::entry_for_method()
-> AbstractInterpreter::method_kind()
这个路径上,methodOopDesc::link_method()找到了该方法对应的解释器入口后会设置到method的_i2i_entry和_from_interpreted_entry。这样这个method就跟最初VM初始化阶段生成的native方法解释器入口关联在一起了。

然后是解释器里的链接阶段。通常在该方法第一次被调用的时候触发。invoke字节码指令执行时要先检查调用目标是否已经resolve好了,没有的话就要做resolution。对应的代码由TemplateTable::resolve_cache_and_index()生成,调用InterpreterRuntime::resolve_invoke()
-> LinkResolver::resolve_invoke()
-> …
-> LinkResolver::resolve_method()
这个路径会把某条invoke
字节码指令的参数的符号链接解析(resolve)为实际的method指针然后存在constant pool cache里。这样,接下来解释器就可以通过解析好的method指针找到from_interpreted_entry()进入native方法的解释器入口。

当某个native方法真的被调用时,一开始它会从解释器进入。这就进到最初提到的generate_native_entry()所生成的代码——native entry处。

它会调用
InterpreterRuntime::prepare_native_call()来获取native函数真正的入口地址
。这里会先检查Method的has_native_function()看之前是否已经在Method对象里记录下了native函数入口地址。如果已有地址的话可能是JNI库在JNI_OnLoad()的时候调用了RegisterNatives()来注册函数地址信息,这样就不需要后面的查找过程(也就不需要遵循Java native函数的命名规则了,例如说一定要"Java_"开头之类的);也有可能这已经不是第一调用该native方法,于是已经经历过下面要说的查找过程了。

如果没有记录下函数地址,就调用NativeLookup::lookup()来寻找native方法真正的目标在什么地方,然后把它记在Method里。其中在NativeLookup::lookup()里会通过NativeLookup::pure_jni_name()来构造出符合JNI规范的函数名,然后通过os::dll_lookup()在查找路径中能找到的动态链接库里去找这个名字对应的地址。

Method里有方法调用次数的计数器,而native entry里有递增这个计数器的逻辑。当一个native方法被调用足够多次之后,HotSpot会为它生成专门的入口(替换掉原本通用的解释器入口)。这种入口叫做native wrapper。Signature相同的native方法共享同一个native wrapper。
CompileBroker::compile_method()
-> AdapterHandlerLibrary::create_native_wrapper()
-> SharedRuntime::generate_native_wrapper()


无论是解释器的native方法入口,还是后面生成出来的native wrapper,在调用native方法时要做的事情都差不多
1、先调整参数的位置,把所有参数向右挪一位(成员方法)或者两位(静态方法)。挪的过程中顺便分配JNI handle block,把对象指针(oop)参数打包成JNIHandle,以便GC能跟踪到传给native方法的对象指针。
2、如果是static方法,把类的指针放到第二个参数的位置上。
3、保持Java调用栈的一些信息(例如set_last_Java_frame())
4、如果是synchronized方法,则对合适的对象加锁(this或class)
5、把JNIEnv参数放到第一个参数的位置上。
6、把线程状态设置到_thread_in_native
7、实际调用native方法
8、从native方法返回后,修正某些寄存器的状态,把返回值挪到合适的地方
9、把线程状态设置到_thread_in_native_trans
10、执行一个membar来清理CPU cache
11、检查是否要进入safepoint
12、如果是synchronized方法,解锁
13、还原Java调用栈的一些信息(例如reset_last_Java_frame())
14、释放JNI handle block
15、如果有未处理的异常就抛异常,没异常就返回

分类:
标签:
请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
据说99.99%的人都会答错的类加载的问题
概述首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。 同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗请仔细注意
Java多线程——并发测试
编写并发程序时候,可以采取和串行程序相同的编程方式。唯一的难点在于,并发程序存在不确定性,这种不确定性会令程序出错的地方远比串行程序多,出现的方式也没有固定规则。那么如何在测试中,尽可能的暴露出这些问
Java多线程知识小抄集(一)
本文主要整理笔者遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1.interr