OpenJDK System. Analysis of loadlibrary source code
System.loadLibrary is used to inform the JVM to load Native so. After so is loaded successfully, you can see that so has been loaded into memory in / proc/self/maps.
Students familiar with system layer development can guess that this is basically equivalent to dlopen/LoadLibrary call. Next, let's analyze it through the OpenJDK source code.
Download the OpenJDK source code: https://github.com/openjdk/jdk
tag: jdk8-b120
1. System.java
Search loadlibrary and find that the runtime has been transferred getRuntime(). loadLibrary0.
public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname); }
2. Runtime.java
Search loadLibrary0. After passing the security detection and illegal path detection, transfer to classloader loadLibrary.
synchronized void loadLibrary0(Class<?> fromClass, String libname) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkLink(libname); } if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); } ClassLoader.loadLibrary(fromClass, libname, false); }
3. ClassLoader.java
Search loadLibrary. After searching through various paths, loadLibrary0 is finally called.
static void loadLibrary(Class<?> fromClass, String name, boolean isAbsolute) { ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); if (sys_paths == null) { usr_paths = initializePath("java.library.path"); sys_paths = initializePath("sun.boot.library.path"); } if (isAbsolute) { if (loadLibrary0(fromClass, new File(name))) { return; } throw new UnsatisfiedLinkError("Can't load library: " + name); } if (loader != null) { String libfilename = loader.findLibrary(name); if (libfilename != null) { File libfile = new File(libfilename); if (!libfile.isAbsolute()) { throw new UnsatisfiedLinkError( "ClassLoader.findLibrary failed to return an absolute path: " + libfilename); } if (loadLibrary0(fromClass, libfile)) { return; } throw new UnsatisfiedLinkError("Can't load " + libfilename); } } for (int i = 0 ; i < sys_paths.length ; i++) { File libfile = new File(sys_paths[i], System.mapLibraryName(name)); if (loadLibrary0(fromClass, libfile)) { return; } libfile = ClassLoaderHelper.mapAlternativeName(libfile); if (libfile != null && loadLibrary0(fromClass, libfile)) { return; } } if (loader != null) { for (int i = 0 ; i < usr_paths.length ; i++) { File libfile = new File(usr_paths[i], System.mapLibraryName(name)); if (loadLibrary0(fromClass, libfile)) { return; } libfile = ClassLoaderHelper.mapAlternativeName(libfile); if (libfile != null && loadLibrary0(fromClass, libfile)) { return; } } } // Oops, it failed throw new UnsatisfiedLinkError("no " + name + " in java.library.path"); }
4. ClassLoader.java
Search loadLibrary0. First call NativeLibrary findBuiltinLib judges whether it has been loaded (findBuiltinLib is located in ClassLoader.c, and finally calls dlsym for judgment). After judging through various permission paths, it synchronously calls lib.load, that is, the load method of NativeLibrary.
private static boolean loadLibrary0(Class<?> fromClass, final File file) { // Check to see if we're attempting to access a static library String name = NativeLibrary.findBuiltinLib(file.getName()); boolean isBuiltin = (name != null); if (!isBuiltin) { boolean exists = AccessController.doPrivileged( new PrivilegedAction<Object>() { public Object run() { return file.exists() ? Boolean.TRUE : null; }}) != null; if (!exists) { return false; } try { name = file.getCanonicalPath(); } catch (IOException e) { return false; } } ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); Vector<NativeLibrary> libs = loader != null ? loader.nativeLibraries : systemNativeLibraries; synchronized (libs) { int size = libs.size(); for (int i = 0; i < size; i++) { NativeLibrary lib = libs.elementAt(i); if (name.equals(lib.name)) { return true; } } synchronized (loadedLibraryNames) { if (loadedLibraryNames.contains(name)) { throw new UnsatisfiedLinkError ("Native Library " + name + " already loaded in another classloader"); } /* If the library is being loaded (must be by the same thread, * because Runtime.load and Runtime.loadLibrary are * synchronous). The reason is can occur is that the JNI_OnLoad * function can cause another loadLibrary invocation. * * Thus we can use a static stack to hold the list of libraries * we are loading. * * If there is a pending load operation for the library, we * immediately return success; otherwise, we raise * UnsatisfiedLinkError. */ int n = nativeLibraryContext.size(); for (int i = 0; i < n; i++) { NativeLibrary lib = nativeLibraryContext.elementAt(i); if (name.equals(lib.name)) { if (loader == lib.fromClass.getClassLoader()) { return true; } else { throw new UnsatisfiedLinkError ("Native Library " + name + " is being loaded in another classloader"); } } } NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin); nativeLibraryContext.push(lib); try { lib.load(name, isBuiltin); } finally { nativeLibraryContext.pop(); } if (lib.loaded) { loadedLibraryNames.addElement(name); libs.addElement(lib); return true; } return false; } } }
5. ClassLoader.java
NativeLibrary is defined in classloader In Java, the load method is declared as native
native void load(String name, boolean isBuiltin);
6. ClassLoader.c
In classloader Find the definition of the load function in C.
- If isBuiltin is true, procHandle is called
- If isBuiltin is false, the JVM is called_ LoadLibrary
Then find JNI in so through findJniFunction_ Onload export function, call it. That is, when writing code, if JNI is defined in C/C + +_ Onload function and export. When the library is loaded, JNI will be called back_ The reason for onload.
/* * Class: java_lang_ClassLoader_NativeLibrary * Method: load * Signature: (Ljava/lang/String;Z)V */ JNIEXPORT void JNICALL Java_java_lang_ClassLoader_00024NativeLibrary_load (JNIEnv *env, jobject this, jstring name, jboolean isBuiltin) { const char *cname; jint jniVersion; jthrowable cause; 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) { JavaVM *jvm; (*env)->GetJavaVM(env, &jvm); jniVersion = (*JNI_OnLoad)(jvm, NULL); } else { jniVersion = 0x00010001; } cause = (*env)->ExceptionOccurred(env); if (cause) { (*env)->ExceptionClear(env); (*env)->Throw(env, cause); if (!isBuiltin) { JVM_UnloadLibrary(handle); } goto done; } 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); }
7. ClassLoader.c
isBuiltin is true. In the initIDs function, the variable procHandle is assigned getProcessHandle()
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; jniVersionID = (*env)->GetFieldID(env, this, "jniVersion", "I"); if (jniVersionID == 0) return JNI_FALSE; loadedID = (*env)->GetFieldID(env, this, "loaded", "Z"); if (loadedID == 0) return JNI_FALSE; procHandle = getProcessHandle(); } return JNI_TRUE; }
8. jni_util_md.c
Continue when isBuiltin is true. In JNI_ util_ Finding the definition of getProcessHandle in md.c is actually equivalent to calling dlopen. The first parameter is NULL, which means that the global symbol table of the process is opened. Later dlsym will find it in the global symbol table, and the second parameter is RTLD_LAZY means delayed binding. The operating system relocates the function address only when a function call actually occurs, which can save the time of relocating the exported function address.
void* getProcessHandle() { static void *procHandle = NULL; if (procHandle != NULL) { return procHandle; } procHandle = (void*)dlopen(NULL, RTLD_LAZY); return procHandle; }
9. jvm.cpp
isBuiltin is false. Call to JVM_LoadLibrary, after parameter judgment and other operations, transfer os::dll_load.
Next, call different systems into their implementation files:
- Os_ in Linux linux. OS:: Linux:: dlopen in CPP_ Helper, and finally call dlopen(filename, RTLD_LAZY)
- Os_ in Windows windows. CPP, and finally call LoadLibrary(name)
- Os_ in Solars solaris. CPP, and finally call dlopen(filename, RTLD_LAZY)
- Os_ in BSD bsd. CPPP, and finally call dlopen(filename, RTLD_LAZY)
Note: in Unix like Linux, Solars and BSD systems, dlopen is finally set and RTLD is set_ Lazy delay loading. Windows also has dll delay loading technology, which can delay loading the whole dll, but it can only be realized through the Link process control of the compiler, and the library needs to be imported dynamically. However, the LoadLibrary function cannot delay loading. From the source code of OpenJDK, it can be seen that delay loading is not started for windows, Therefore, it is less efficient to load the shared library in windows (all global variables and functions of Linux and other systems are exported by default, while Windows Dll is not exported by default, and the export needs to be specified manually, so the amount is generally not large).
JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name)) //%note jvm_ct JVMWrapper2("JVM_LoadLibrary (%s)", name); char ebuf[1024]; void *load_result; { ThreadToNativeFromVM ttnfvm(thread); load_result = os::dll_load(name, ebuf, sizeof ebuf); } if (load_result == NULL) { char msg[1024]; jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf); // Since 'ebuf' may contain a string encoded using // platform encoding scheme, we need to pass // Exceptions::unsafe_to_utf8 to the new_exception method // as the last argument. See bug 6367357. Handle h_exception = Exceptions::new_exception(thread, vmSymbols::java_lang_UnsatisfiedLinkError(), msg, Exceptions::unsafe_to_utf8); THROW_HANDLE_0(h_exception); } return load_result; JVM_END