[讨论] [HotSpot VM] 能解释下java层面调用native方法怎么找到本地库中对应的方法的吗
nijiaben
2013-04-21
一直有个疑惑,在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代码在哪里呢,麻烦@RednaxelaFX帮忙解释下吧,谢谢啦 |
|
panggezi
2013-04-23
在windows平台上,JVM会先调用LoadLibrary,然后GetProcAddress,然后调用方法,其他平台有各自类似的api接口。
|
|
nijiaben
2013-04-23
panggezi 写道 在windows平台上,JVM会先调用LoadLibrary,然后GetProcAddress,然后调用方法,其他平台有各自类似的api接口。
谢谢哥们的回答,我在nativeLookup.cpp中找到如下方法 char* NativeLookup::pure_jni_name(methodHandle method) { stringStream st; // Prefix st.print("Java_"); // Klass name mangle_name_on(&st, method->klass_name()); st.print("_"); // Method name mangle_name_on(&st, method->name()); return st.as_string(); } gdb打个断点可以跟跟 |
|
RednaxelaFX
2013-04-24
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、如果有未处理的异常就抛异常,没异常就返回 |
|
xiaoxia19920920
2013-11-09
|
|
nijiaben
2015-04-09
引用 1、先调整参数的位置,把所有参数向右挪一位(成员方法)或者两位(静态方法)。挪的过程中顺便分配JNI handle block,把对象指针(oop)参数打包成JNIHandle,以便GC能跟踪到传给 挪的过程中顺便分配JNI handle block,把对象指针(oop)参数打包成JNIHandle,这段代码没有找到地方,具体在哪个位置呢 |