首页 > 代码库 > 读书笔记之inside JVM(5)

读书笔记之inside JVM(5)

调用JNI的时候,通常我们使用System.loadLibrary(String libname)来load JNI library, 同样也可以使用System.load(String fileName)来load JNI library,两者的区别是一个只需要设置库的名字,比如如果libA.so 只要输入A就可以了,而libA.so的位置可以同过设置 java.library.path 或者 sun.boot.library.path,后者输入的是完整路经的文件名。

而不论用什么方法,最后JNI 库是通过classloader 来加载的。

[java]   view plain copy
  1. static void loadLibrary(Class fromClass, String name,  
  2.                 boolean isAbsolute) {}  

每个classloader 对象都有自己的nativeLibrary 数组,一个全局的systemNativeLibrary 数组,一个全局的已经加载过的loadLibraryNames数组,和一个正在加载过程中的记录栈nativeLibraryContext

对同一个classloader 对象可以重复加载相同的库,对不同的classloader只可以加载一次相同的库。

1. 这里定义的相同的库是指相同路经下的同一个文件

2.  这里同样指出的是同一个classloader对象,而不是同一种classloader类型,比如说如果一种classloader类型初始化成2个classloader对象,那么这两个对象就不能重复加载相同的库。

3. 重复加载,并不代表真的重复加载,而是代码中保护

[java]   view plain copy
  1. for (int i = 0; i < size; i++) {  
  2.             NativeLibrary lib = (NativeLibrary)libs.elementAt(i);  
  3.         if (name.equals(lib.name)) {  
  4.             return true;  
  5.         }  
  6.         }  

4. 如果加载其他classloader已经加载过的库,会抛出 UnsatisfiedLinkError ERROR


在tomcat上,在不同的war包里,想加载相同的库文件,因为在 tomcat上是使用不同的classloader的对象去加载不同的war包,建议库文件放置在不同的路径通过System.load去加载。

在博客java JNI (一)虚拟机中classloader的JNILibrary 中讨论了java中的Library 是由classloader 来load的,那我们来看看 classloader是如何去load 一个library的

ClassLoader.c  

[cpp]   view plain copy
  1. JNIEXPORT void JNICALL   
  2. Java_java_lang_ClassLoader_00024NativeLibrary_load  
  3.   (JNIEnv *env, jobject this, jstring name)  
  4. {  
  5.     const char *cname;  
  6.     jint jniVersion;  
  7.     jthrowable cause;  
  8.     void * handle;  
  9.   
  10.     if (!initIDs(env))  
  11.         return;  
  12.   
  13.     cname = JNU_GetStringPlatformChars(env, name, 0);  
  14.     if (cname == 0)  
  15.         return;  
  16.     handle = JVM_LoadLibrary(cname);  
  17.     if (handle) {  
  18.         const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;  
  19.         JNI_OnLoad_t JNI_OnLoad;  
  20.     int i;  
  21.     for (i = 0; i < sizeof(onLoadSymbols) / sizeof(char *); i++) {  
  22.         JNI_OnLoad = (JNI_OnLoad_t)   
  23.             JVM_FindLibraryEntry(handle, onLoadSymbols[i]);  
  24.         if (JNI_OnLoad) {  
  25.             break;  
  26.         }  
  27.     }  
  28.     if (JNI_OnLoad) {  
  29.         JavaVM *jvm;  
  30.         (*env)->GetJavaVM(env, &jvm);  
  31.         jniVersion = (*JNI_OnLoad)(jvm, NULL);  
  32.     } else {  
  33.         jniVersion = 0x00010001;  
  34.     }  
  35.   
  36.     cause = (*env)->ExceptionOccurred(env);  
  37.     if (cause) {  
  38.         (*env)->ExceptionClear(env);  
  39.         (*env)->Throw(env, cause);  
  40.         JVM_UnloadLibrary(handle);  
  41.         goto done;  
  42.     }  
  43.      
  44.     if (!JVM_IsSupportedJNIVersion(jniVersion)) {  
  45.         char msg[256];  
  46.         jio_snprintf(msg, sizeof(msg),  
  47.              "unsupported JNI version 0x%08X required by %s",  
  48.              jniVersion, cname);  
  49.         JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);  
  50.         JVM_UnloadLibrary(handle);  
  51.         goto done;  
  52.     }  
  53.     (*env)->SetIntField(env, this, jniVersionID, jniVersion);  
  54.     } else {  
  55.     cause = (*env)->ExceptionOccurred(env);  
  56.     if (cause) {  
  57.         (*env)->ExceptionClear(env);  
  58.         (*env)->SetLongField(env, this, handleID, (jlong)NULL);  
  59.         (*env)->Throw(env, cause);  
  60.     }  
  61.     goto done;  
  62.     }  
  63.     (*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));  
  64.   
  65.  done:  
  66.     JNU_ReleaseStringPlatformChars(env, name, cname);  
  67. }  

1. JVM_LoadLibrary 

jvm中load library 核心函数,实现也非常简单,在linux下调用了系统函数dlopen去打开库文件,详细可参考方法

[cpp]   view plain copy
  1. void * os::dll_load(const char *filename, char *ebuf, int ebuflen)  

2. JVM_FindLibraryEntry 

JVM在加载库文件时候,会去尝试查找库中的JNI_ONLOAD方法的地址,而在Linux中调用了dlsym函数通过前面的dlopen加载库的指针去获取方法的地址,而dlsym在glibc2.0是非线程安全的,需要锁的保护,虽然在java中加载库已经有锁的保护,但只是针对同一个classloader对象的细粒度锁。

[cpp]   view plain copy
  1. void* os::dll_lookup(void* handle, const char* name) {  
  2.   pthread_mutex_lock(&dl_mutex);  
  3.   void* res = dlsym(handle, name);  
  4.   pthread_mutex_unlock(&dl_mutex);  
  5.   return res;  
  6. }  

3. 方法JNI_OnLoad

JVM提供了一种方式允许你在加载库文件的时候做一些你想做的事情,也就是JNI_OnLoad方法

在2中提到过在加载动态链接库,JVM会去尝试查找JNI_OnLoad方法,同时也会调用该函数,这样你个人可以在函数里做一些初始化的事情,比如register native方法。

[cpp]   view plain copy
  1. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)  
  2. {}  
JNI_OnLoad中返回的是JNI 的version,在1.6版本的情况下支持如下 
[cpp]   view plain copy
  1. jboolean Threads::is_supported_jni_version(jint version) {  
  2.   if (version == JNI_VERSION_1_2) return JNI_TRUE;  
  3.   if (version == JNI_VERSION_1_4) return JNI_TRUE;  
  4.   if (version == JNI_VERSION_1_6) return JNI_TRUE;  
  5.   return JNI_FALSE;  
  6. }  

完整的加载过程就是

首先先加载动态链接库,尝试查找JNI_OnLoad方法,并且运行方法,对我们来说从而实现可以自定义的初始化方法。

 

我们常用javah去生成JNI的头文件,然后去实现自己定义的JNI方法,使用这种方式比较传统,我们可以看到定义的格式甚至连名字都必须按照规范

[cpp]   view plain copy
  1. JNIEXPORT jint JNICALL Java_test_symlink  
  2.   (JNIEnv *, jobject, jstring, jstring);  


 

完整的结构是Java_classpath_classname_native method name,这样才能当jvm运行的时候根据这个命名规则去找到对应的native的方法。

实际上jvm也同时提供了直接RegisterNative方法手动的注册native方法

下面是一个代码的例子

[cpp]   view plain copy
  1. static JNINativeMethod methods[] = {  
  2.     {"retrieveDirectives",  "()Ljava/lang/AssertionStatusDirectives;", (void *)&JVM_AssertionStatusDirectives}  
  3. };  
  4.   
  5.   
  6.     (*env)->RegisterNatives(env, cls, methods,   
  7.                 sizeof(methods)/sizeof(JNINativeMethod));  


 

RegisterNative 函数中的参数 

RegisterNative(JNIEnv, jclass cls, JNINativeMethod *methods,  jint number)

1. methods 是一个二维数组,代表着这个class里的每一个native方法所对应的实现的方法,在前面的例子中表示,一个native 方法retrieveDiretives, 返回值为AssertionStatusDirectives, 所对应的执行的本地方法是JVM_AssertionStatusDirectives

2. 后面的number 代表要指定的native的数量


RegisterNative的实现

RegisterNative 的实现非常简单,就是将class里面native的方法的地址+1指向执行的c代码的函数地址也就是上面的&JVM_AssertionStatusDirectives

[cpp]   view plain copy
  1. address* native_function_addr() const          { assert(is_native(), "must be native"); return (address*) (this+1);   


 

这是jvm当初始化类的时候,class的调用层级关系

instanceKlass::initialize() 
     -> instanceKlass::initialize_impl() 
           -> instanceKlass::link_class() 
                 -> instanceKlass::link_class_impl() 
                      -> instanceKlass::rewrite_class() 
                           -> Rewriter::rewrite() 
                                -> Rewriter::Rewriter() 
                                    -> methodOopDesc::link_method() 

在方法methodOopDesc::link_method 设置到了对应的natvive方法的解释entry

[cpp]   view plain copy
  1. void methodOopDesc::link_method(methodHandle h_method, TRAPS) {  
  2.   assert(_i2i_entry == NULL, "should only be called once");  
  3.   assert(_adapter == NULL, "init‘d to NULL" );  
  4.   assert( _code == NULL, "nothing compiled yet" );  
  5.   
  6.   // Setup interpreter entrypoint  
  7.   assert(this == h_method(), "wrong h_method()" );  
  8.   address entry = Interpreter::entry_for_method(h_method); //找到对应的方法 entry  
  9.   assert(entry != NULL, "interpreter entry must be non-null");  
  10.   // Sets both _i2i_entry and _from_interpreted_entry  
  11.   set_interpreter_entry(entry); //并且把entry设置到了methodoop中  
  12.   ...  
  13. }  

找到对应的方法类型的entry

函数entry_for_method 是从_entry_table数组中找到对应的entry

[cpp]   view plain copy
  1. static address    entry_for_method(methodHandle m)            { return _entry_table[method_kind(m)]; }  


在函数中TemplateInterpreterGenerator::generate_all,我们可以看到初始化了_entry_table entry数组,而这是在jvm初始化的时候(jint init_globals())初始化的

[cpp]   view plain copy
  1. #define method_entry(kind)                                                                    \  
  2.   { CodeletMark cm(_masm, "method entry point (kind = " #kind ")");                    \  
  3.     Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind);  \  
  4.   }  
  5.   
  6.   // all non-native method kinds  
  7.   method_entry(zerolocals)  
  8.   method_entry(zerolocals_synchronized)  
  9.   method_entry(empty)  
  10.   method_entry(accessor)  
  11.   method_entry(abstract)  
  12.   method_entry(method_handle)  
  13.   method_entry(java_lang_math_sin  )  
  14.   method_entry(java_lang_math_cos  )  
  15.   method_entry(java_lang_math_tan  )  
  16.   method_entry(java_lang_math_abs  )  
  17.   method_entry(java_lang_math_sqrt )  
  18.   method_entry(java_lang_math_log  )  
  19.   method_entry(java_lang_math_log10)  
  20.   
  21.   // all native method kinds (must be one contiguous block)  
  22.   Interpreter::_native_entry_begin = Interpreter::code()->code_end();  
  23.   method_entry(native)  
  24.   method_entry(native_synchronized)  
  25.   Interpreter::_native_entry_end = Interpreter::code()->code_end();  
  26.   
  27. #undef method_entry  


而对应的不同的方法,使用不同的entry 是在函数generate_method_entry里定义的

[cpp]   view plain copy
  1. address AbstractInterpreterGenerator::generate_method_entry(  
  2.                                         AbstractInterpreter::MethodKind kind) {  
  3.   // determine code generation flags  
  4.   bool synchronized = false;  
  5.   address entry_point = NULL;  
  6.   
  7.   switch (kind) {  
  8.   case Interpreter::zerolocals             :                                                                             break;  
  9.   case Interpreter::zerolocals_synchronized: synchronized = true;                                                        break;  
  10.   case Interpreter::native                 : entry_point = ((InterpreterGenerator*) this)->generate_native_entry(false); break;  
  11.   case Interpreter::native_synchronized    : entry_point = ((InterpreterGenerator*) this)->generate_native_entry(true);  break;  
  12.   case Interpreter::empty                  : entry_point = ((InterpreterGenerator*) this)->generate_empty_entry();       break;  
  13.   case Interpreter::accessor               : entry_point = ((InterpreterGenerator*) this)->generate_accessor_entry();    break;  
  14.   case Interpreter::abstract               : entry_point = ((InterpreterGenerator*) this)->generate_abstract_entry();    break;  
  15.   case Interpreter::method_handle          : entry_point = ((InterpreterGenerator*) this)->generate_method_handle_entry();break;  
  16.   
  17.   case Interpreter::java_lang_math_sin     : // fall thru  
  18.   case Interpreter::java_lang_math_cos     : // fall thru  
  19.   case Interpreter::java_lang_math_tan     : // fall thru  
  20.   case Interpreter::java_lang_math_abs     : // fall thru  
  21.   case Interpreter::java_lang_math_log     : // fall thru  
  22.   case Interpreter::java_lang_math_log10   : // fall thru  
  23.   case Interpreter::java_lang_math_sqrt    : entry_point = ((InterpreterGenerator*) this)->generate_math_entry(kind);    break;  
  24.   default                                  : ShouldNotReachHere();                                                       break;  
  25.   }  
  26.   
  27.   if (entry_point) {  
  28.     return entry_point;  
  29.   }  
  30.   
  31.   return ((InterpreterGenerator*) this)->  
  32.                                 generate_normal_entry(synchronized);  
  33. }  


我们可以看到在 native,和native_synchronized的情况下,使用了generate_native_entry

在methodoop设置了entry

在方法methodOopDesc::link_method 中,设置了_i2i_entry,和_from_interpreted_entry为entry 也是就在native的情况下设置了generate_native_entry
[cpp]   view plain copy
  1. void set_interpreter_entry(address entry)      { _i2i_entry = entry;  _from_interpreted_entry = entry; }  


 

Hotspot主要有两种解释器,而下面我们主要讨论的是 Template Intepreter也叫asm interprete解释器, 文章下面的介绍基本都是基于template解释器

我们举一个invokespecial的例子,下面是templateTable方法解释invokespecial的代码

[cpp]   view plain copy
  1. void TemplateTable::invokespecial(int byte_no) {  
  2.   transition(vtos, vtos);  
  3.   assert(byte_no == f1_byte, "use this argument");  
  4.   prepare_invoke(rbx, noreg, byte_no);  
  5.   // do the call  
  6.   __ verify_oop(rbx);  
  7.   __ profile_call(rax);  
  8.   __ jump_from_interpreted(rbx, rax);  
  9. }  

函数prepare_invoke

函数prepare_invoke的层级调用关系

TemplateTable::prepare_invoke

         -> TemplateTable::load_invoke_cp_cache_entry

                   -> TemplateTable::resolve_cache_and_index

在函数中resolve_cache_and_index可以看到

1. 首先先检查constantpoolcache,是否将方法指针保存到到线程的constantpoolcache里,如果有在方法里会使用jcc跳转到Label resolved去,而Lable resolved 在方法第一次运行结束后bind到函数的末尾。

2. 如果cache里没有那么会尝试用interpreterRuntime:resolve_invoke去找到正确的method, 并保存到constant pool cache 里

[cpp]   view plain copy
  1. case Bytecodes::_invokevirtual:  
  2. case Bytecodes::_invokespecial:  
  3. case Bytecodes::_invokestatic:  
  4. case Bytecodes::_invokeinterface:  
  5.   entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_invoke);  
  6.   break;  

而函数
interpreterRuntime:resolve_invoke
         -->LinkResolver::resolve_invoke
                -->LinkResolver::resolve_special_call
                       -->LinkResolver::linktime_resolve_special_method
                               -->LinkResolver::resolve_method
不论什么调用方式,最后都会调用LinkResolver::resolve_method 找到真实的调用方法,通过runtime_resolve_special_method把method指针作为methodhandle存放到CallInfo 中传回InterperterRuntime::resolve_invoke中,同时在CallInfo::set_common当设置-Xcomp情况下,决定是否需要编译方法。

我们可以看到方法prepare_invoke,已经找到了methodoop指针并且存放到寄存器rbx中

函数jump_from_interpreted

[cpp]   view plain copy
  1. void InterpreterMacroAssembler::jump_from_interpreted(Register method, Register temp) {  
  2.   prepare_to_jump_from_interpreted();  
  3.   
  4.   if (JvmtiExport::can_post_interpreter_events()) {  
  5.     Label run_compiled_code;  
  6.     // JVMTI events, such as single-stepping, are implemented partly by avoiding running  
  7.     // compiled code in threads for which the event is enabled.  Check here for  
  8.     // interp_only_mode if these events CAN be enabled.  
  9.     get_thread(temp);  
  10.     // interp_only is an int, on little endian it is sufficient to test the byte only  
  11.     // Is a cmpl faster (ce  
  12.     cmpb(Address(temp, JavaThread::interp_only_mode_offset()), 0);  
  13.     jcc(Assembler::zero, run_compiled_code);  
  14.     jmp(Address(method, methodOopDesc::interpreter_entry_offset()));  
  15.     bind(run_compiled_code);  
  16.   }  
  17.   
  18.   jmp(Address(method, methodOopDesc::from_interpreted_offset()));  
  19.   
  20. }  
我们看到跳转到了methodoop中的_from_interpreted_entry,也就是在前面的博客里(java JNI (四) 初始化JNI方法)说的generate_native_entry 中


在这篇博客并没有太多的涉及到native方法的调用,而是asm解释器在解释一个方法的时候如何link到所对应method,并且找到处理method的entry。

在前面的博客中已经提到过JNI的entry是generate_native_entry,也就是说方法generate_native_entry才是最终调用的我们自己写的库文件里的方法

针对不同的解释器的类型,会调用不同的generate_native_entry,下面主要讨论的还是以template interpreter为主

如果是X86的,可以参考templateInterpreter_x86_64.cpp

[cpp]   view plain copy
  1. address InterpreterGenerator::generate_native_entry(bool synchronized) {  
  2. ....  
  3.   {  
  4.     Label L;  
  5.     __ movptr(rax, Address(method, methodOopDesc::native_function_offset()));  
  6.     ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry());  
  7.     __ movptr(rscratch2, unsatisfied.addr());  
  8.     __ cmpptr(rax, rscratch2);  
  9.     __ jcc(Assembler::notEqual, L);  
  10.     __ call_VM(noreg,  
  11.                CAST_FROM_FN_PTR(address,  
  12.                                 InterpreterRuntime::prepare_native_call),  
  13.                method);  
  14.     __ get_method(method);  
  15.     __ verify_oop(method);  
  16.     __ movptr(rax, Address(method, methodOopDesc::native_function_offset()));  
  17.     __ bind(L);  
  18.   }  
  19. .....  
  20.   __ call(rax);  
  21. .....  
  22. }  

具体我们来看prepare_native_call方法的实现
[cpp]   view plain copy
  1. IRT_ENTRY(void, InterpreterRuntime::prepare_native_call(JavaThread* thread, methodOopDesc* method))  
  2.   methodHandle m(thread, method);  
  3.   assert(m->is_native(), "sanity check");  
  4.   // lookup native function entry point if it doesn‘t exist  
  5.   bool in_base_library;  
  6.   if (!m->has_native_function()) {  
  7.     NativeLookup::lookup(m, in_base_library, CHECK);  
  8.   }  
  9.   // make sure signature handler is installed  
  10.   SignatureHandlerLibrary::add(m);  
  11.   // The interpreter entry point checks the signature handler first,  
  12.   // before trying to fetch the native entry point and klass mirror.  
  13.   // We must set the signature handler last, so that multiple processors  
  14.   // preparing the same method will be sure to see non-null entry & mirror.  
  15. IRT_END  

在代码中

if (!m->has_native_function()) {
    NativeLookup::lookup(m, in_base_library, CHECK);
  }

首先先检查一下是不是已经在method里面定义了native的方法,也就是我们前面提到的JNI中的(RegisterNatives方法)中是不是已经单独RegisterNatives注册了native方法

如果没有的话,将按照javah生成的JNI头文件里的方法的名字来绑定,也就是lookup里做的事情,然后设置回method的native的方法中去,保证调用只初始化一次,不是每次调用都去查找一遍

这样在方法prepare_native_call 中,我们可以和前面的博客(java JNI 实现原理 (三) JNI中的RegisterNatives方法),完整的联系起来。


下面大概的介绍一下,完整的native call的过程

a.  初始化一些参数,把方法的一些信息放到对应的寄存器上

b.  检查是否需要锁,如果需要,锁到对应的object

c.  设置几个handler, signature hanlder, result handler, mirror handler(static 方法)

d.  找到 native 方法的指针,设置到rax寄存器中

e.  传入JNIEnv 对象在native方法第一个参数(因为JNIEnv 对象在Java 代码中的native 方法没有,而在自己定义的native方法里用来取得jni的运行环境的,所以需要在这里额外传入)

f.  设置java栈信息 last_java_frame

g.  设置线程信息为_thread_in_native

h.  运行native 方法

i.   在多核的情况下,使用Membar清楚内存cache

j.   检查是否在safepoint的点

k. 还原java 栈信息 reset_last_java_frame

l.  对结果进行一些处理

m. 处理过程中的异常

n.  释放锁

o.  调用result hanlder 的到结果

 

读书笔记之inside JVM(5)