OpenJDK System.loadLibrary源码剖析
System.loadLibrary是用于通知JVM加载Native so的,so加载成功后,在/proc/self/maps中可以看到so已经被加载到内存中。
熟悉系统层开发同学可以猜到,这基本等同于dlopen/LoadLibrary调用,接下来我们通过OpenJDK源码来分析一下。
下载OpenJDK源码:https://github.com/openjdk/jdk
tag:jdk8-b120
1. System.java
搜索loadLibrary,发现转调了Runtime.getRuntime().loadLibrary0。
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
2. Runtime.java
搜索loadLibrary0。通过安全检测,非法路径检测后,转调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
搜索loadLibrary。通过各类路径查找之后,最终调用loadLibrary0。
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
搜索loadLibrary0。首先调用NativeLibrary.findBuiltinLib判断是不是已经被加载过了(findBuiltinLib位于ClassLoader.c中,最终调用dlsym进行判断),通过各类权限路径判断后,同步调用lib.load,即NativeLibrary的load方法。
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定义在ClassLoader.java中,load方法被声明为native
native void load(String name, boolean isBuiltin);
6. ClassLoader.c
在ClassLoader.c中找到load函数的定义。
- 如果isBuiltin为true,则调用procHandle
- 如果isBuiltin为false,则调用JVM_LoadLibrary
然后通过findJniFunction找到so中的JNI_OnLoad导出函数,调用之。这也就是在写代码时,如果C/C++中有定义JNI_OnLoad函数并导出,在库被load时,将回调JNI_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为true情况。在initIDs函数中变量procHandle被赋值为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
继续isBuiltin为true情况。在jni_util_md.c中找到getProcessHandle的定义,实际上等同于调用dlopen,第一个参数为NULL,表示打开进程的全局符号表,后续dlsym将在全局符号表中查找,第二个参数RTLD_LAZY表示延迟绑定,在真实发生函数调用时,操作系统才对函数地址进行重定位,能节省导出函数地址重定位的耗时。
void* getProcessHandle() {
static void *procHandle = NULL;
if (procHandle != NULL) {
return procHandle;
}
procHandle = (void*)dlopen(NULL, RTLD_LAZY);
return procHandle;
}
9. jvm.cpp
isBuiltin为false情况。调用到JVM_LoadLibrary,进行参数判断等操作后,转调os::dll_load。
接下来不同系统调入各自的实现文件中:
- Linux中调入os_linux.cpp中的os::Linux::dlopen_helper,最终调用dlopen(filename, RTLD_LAZY)
- Windows中调入os_windows.cpp,最终调用LoadLibrary(name)
- Solars中调入os_solaris.cpp,最终调用dlopen(filename, RTLD_LAZY)
- BSD中调入os_bsd.cppp,最终调用dlopen(filename, RTLD_LAZY)
注意:类Unix的Linux、Solars、BSD系统中,最终都同样调到dlopen,并且设置RTLD_LAZY延迟加载。Windows中也有dll延迟加载技术,可以延迟加载整个dll,不过只能通过编译器的Link过程控制实现,并且需要动态导入库,而LoadLibrary函数是无法延迟加载的,从OpenJDK的源码看出,此处对Windows并未开启延迟加载,所以其在Windows中加载共享库的效率稍低(Linux等系统的所有全局变量与函数都是默认导出的,而Windows Dll默认都是不导出的,需要手动指定导出,所以量一般不会很大)。
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