只需一篇就让你详细了解 Java 中 so 文件的加载原理( 六 )


 
// native/java/lang/ClassLoader.cstatic void *findJniFunction(JNIEnv *env, void *handle,const char *cname, jboolean isLoad) {const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS;void *entryName = NULL;...// 如果是加载,则到 JNI_ONLOAD_SYMBOLS 中获取函数数组和长度if (isLoad) {syms = onLoadSymbols;symsLen = sizeof(onLoadSymbols) / sizeof(char *);} else {// 反之,则到 JNI_ONUNLOAD_SYMBOLS 中获取卸载函数数组和长度syms = onUnloadSymbols;symsLen = sizeof(onUnloadSymbols) / sizeof(char *);}// 遍历该数组,调用 JVM_FindLibraryEntry()// 逐个查找 JNI_On(Un)Load<_libname> function 是否存在for (i = 0; i < symsLen; i++) {// cname + sym + '_' + ''if ((len = (cname != NULL ? strlen(cname) : 0) + strlen(syms[i]) + 2) >FILENAME_MAX) {goto done;}jniFunctionName = malloc(len);if (jniFunctionName == NULL) {JNU_ThrowOutOfMemoryError(env, NULL);goto done;}buildJniFunctionName(syms[i], cname, jniFunctionName);entryName = JVM_FindLibraryEntry(handle, jniFunctionName);free(jniFunctionName);if(entryName) {break;}} done:// 如果没有找到,默认返回 NULLreturn entryName;} 
JVM_FindLibraryEntry()
 
JVM_FindLibraryEntry() 调用的是平台相关的 dll_lookup(),依据 library 指针和 function 名称 。
 
// vm/prims/jvm.cppJVM_LEAF(void*, JVM_FindLibraryEntry(void* handle, const char* name))JVMWrapper2("JVM_FindLibraryEntry (%s)", name);return os::dll_lookup(handle, name);JVM_END 
/ NativeLibrary#load() /
 
NativeLibrary 是定义在 ClassLoader 内的静态内部类,其代表着已加载 library 的实例,包含了该 library 的指针、所需的 JNI 版本、加载的 Class 来源、名称、是否是内置 library、是否加载过重要信息 。以及核心的加载 load、卸载 unload native 实现 。
 
// java/lang/ClassLoader.javastatic class NativeLibrary {long handle;private int jniVersion;private final Class<?> fromClass;String name;boolean isBuiltin;boolean loaded;native void load(String name, boolean isBuiltin);native void unload(String name, boolean isBuiltin);static native String findBuiltinLib(String name);...} 
本章节我们着重看下 load() 的关键实现:
 
1.首先调用 initIDs() 初始化 ID 等基本数据
 

  • 如果 ClassLoader$NativeLibrary 内部类、handle 等属性有一不存在的话,返回 FALSE 并结束加载
  • 通过检查的话初始化 procHandle 指针
 
2.其次通过
JNU_GetStringPlatformChars() 将 String 类型的 library 名称转为 char 类型,如果名称为空的话结束加载
 
【只需一篇就让你详细了解 Java 中 so 文件的加载原理】3.如果不是内置的 so,需要调用 JVM_LoadLibrary() 加载得到指针(见下章节),反之沿用上述的 procHandle 指针即可
 
4.如果 so 指针存在的话,通过 findJniFunction() 和指针参数获取 JNI_OnLoad() 的地址 。如果 JNI_OnLoad() 获取成功,则调用它并得到该 so 要求的 jniVersion 。反之设置为默认值 0x00010001,即 JNI_VERSION_1_1,1.1 。接着调用 JVM_IsSupportedJNIVersion() 检查 JVM 是否支持该版本,调用的是 Threads 的 is_supported_jni_version_including_1_1() 。如果不支持或者是内置 so 同时版本低于 1.8,抛出 UnsatisfiedLinkError:
 
unsupported JNI version xxx required by yyy
 
反之表示加载成功 。
 
5.反之,抛出异常 ExceptionOccurred 。
 
// native/java/lang/ClassLoader.cJava_java_lang_ClassLoader_00024NativeLibrary_load(JNIEnv *env, jobject this, jstring name, jboolean isBuiltin){const char *cname;...void * handle;if (!initIDs(env)) return;cname = JNU_GetStringPlatformChars(env, name, 0);if (cname == 0) return;handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);if (handle) {JNI_OnLoad_t JNI_OnLoad;JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle,isBuiltin ? cname : NULL,JNI_TRUE);if (JNI_OnLoad) {...jniVersion = (*JNI_OnLoad)(jvm, NULL);} else {jniVersion = 0x00010001;}...if (!JVM_IsSupportedJNIVersion(jniVersion) ||(isBuiltin && jniVersion < JNI_VERSION_1_8)) {char msg[256];jio_snprintf(msg, sizeof(msg),"unsupported JNI version 0x%08X required by %s",jniVersion, cname);JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);if (!isBuiltin) {JVM_UnloadLibrary(handle);}goto done;}(*env)->SetIntField(env, this, jniVersionID, jniVersion);} else {cause = (*env)->ExceptionOccurred(env);if (cause) {(*env)->ExceptionClear(env);(*env)->SetLongField(env, this, handleID, (jlong)0);(*env)->Throw(env, cause);}goto done;}(*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));(*env)->SetBooleanField(env, this, loadedID, JNI_TRUE); done:JNU_ReleaseStringPlatformChars(env, name, cname);}static jboolean initIDs(JNIEnv *env){if (handleID == 0) {jclass this =(*env)->FindClass(env, "java/lang/ClassLoader$NativeLibrary");if (this == 0)return JNI_FALSE;handleID = (*env)->GetFieldID(env, this, "handle", "J");if (handleID == 0)return JNI_FALSE;...procHandle = getProcessHandle();}return JNI_TRUE;}


推荐阅读