OpenJDK System.loadLibrary源码剖析

2 篇文章 0 订阅
1 篇文章 0 订阅

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值