OpenJDK System.loadLibrary源码剖析_xt_xiaotian的博客-程序员ITS301_system.load openjdk

技术标签: JDK  JVM  Java  java  后端  开发语言  

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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xt_xiaotian/article/details/122194883

智能推荐

C++ STL nth_element原理与应用_linkfqy的博客-程序员ITS301_nth_element原理

LZ最近的考试中,某题可以通过调用nth_element()来水过70%的数据 但是LZ并不会啊(历史总是惊人的相似) 于是就有了这篇blog在编写代码时,有时会有“在一个无序表中快速得到第K小的元素”的需求 而直接排序 不能水过一些测试点 时间不能承受 于是STL的algorithm头文件就给我们提供了nth_element()这样的部分排序函数调用的正确姿势是这样的:nth_elemen

Spring自定义命名空间_liuhmmjj的博客-程序员ITS301

Spring在解析xml文件中的标签的时候会区分当前的标签是四种基本标签(import、alias、bean和beans)还是自定义标签,如果是自定义标签,则会按照自定义标签的逻辑解析当前的标签。另外,即使是bean标签,其也可以使用自定义的属性或者使用自定义的子标签。本文将对自定义标签和自定义属性的使用方式进行讲解,并且会从源码的角度对自定义标签和自定义属性的实现方式进行讲解。Spring框...

flutter动画 Animation,Curve,AnimationController,Tween,Hero_litter_lj的博客-程序员ITS301_flutter animation curve

1.AnimationAnimation是一个抽象类,它本身和UI渲染没有任何关系,而它主要的功能是保存动画的插值和状态;其中一个比较常用的Animation类是Animation&lt;double&gt;。Animation对象是一个在一段时间内依次生成一个区间(Tween)之间值的类。Animation对象在整个动画执行过程中输出的值可以是线性的、曲线的、一个步进函数或者任何其他曲线函数...

黑马程序员————接口和多态_feitianmao627的博客-程序员ITS301_黑马程序员猫和狗接口

接口一.接口的定义:接口可以被认为是一个特殊的抽象类。当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。接口使用interface来表示,子类中用implements实现。​​二.格式特点:接口中常见定义:常量,抽象方法。接口中的成员都有固定修饰符。常量:public static final。方法:public abstract

bochs上网及配置_dayansi2855的博客-程序员ITS301

下载并安装bochs2.6:(不能是更高版本)创建bochs 时注意勾选Dlx linux Demo,但是其文件bochsrc.bxrc中无Ne2k网卡选项,这一段要自己添加,详情见后。先确定我们电脑里的真实网卡:开始-&gt;程序-&gt;附件-&gt;命令提示符DOS窗口下运行ipconfig /all记住真实网卡是VIA下载安装wincap,这个是...

php后端登录设计,Vue+php 后端PHP登录接口编写_weixin_39975683的博客-程序员ITS301

session_start();header('Access-Control-Allow-Origin: *');error_reporting(E_ALL &amp; ~E_NOTICE);//header("Content-type: text/html; charset=gbk");/*注意:本php是gbk编码的,因为mysise就是gbk编码没办法json_encode()里面的值要先转...

随便推点

嵌入式系统学习笔记【华电】——《第五章 实时操作系统μC/OS-II》_智慧的旋风的博客-程序员ITS301_μc/os-ii操作系统

第五章 实时操作系统μC/OS-II1、μC/OS-II概述μC/OS:微控制器操作系统。其性能特点:(1)开源(2)可移植(3)可固化(4)可裁剪(5)…2、任务管理创建任务:OSTaskCreate()、OSTaskCreateExt()OSTaskCreate()、OSTaskCreateExt()OSTaskCreate()、OSTaskCreateExt()主函数是无限循环。删除任务:OSTaskDel()OSTaskDel()OSTaskDel(),只是内核看不到了而已创

jQuery中$(document).ready与Javascript中window.onload区别_我爱默小兜的博客-程序员ITS301

JQuery中$(document).ready与Javascript中window.onload道理有什么区别呢?网上的说法很多,但是发现使用不同版本jquery,效果有完全不一样,很难去回答一下问题,我觉得有必要总结一下1.window.onload到底是什么时候触发?2.jQuery中ready到底是什么时候触发?3.不同版本jQuery中$(document

Vision-and-Language Navigation: Interpreting visually-grounded navigation instructions in real env_yyyyyyyyXu的博客-程序员ITS301

Introduction来自 澳大利亚阿德莱德大学,Vision-and-Language Navigation(VLN) 的一篇工作,发表在CVPR 2018。项目主页:地址个人主页:地址仿真器开源:地址Motivation 提出了Matterport3D Simulator,一个基于真实场景图的大规模强化学习环境。和之前的合成的强化学习环境相比,真实图像的环境更加具有视觉和语义...

PyCharm必看--PyCharm力扣刷题篇_HYXR的博客-程序员ITS301_pycharm刷题

PyCharm必看–PyCharm力扣刷题篇安装力扣插件,打开 settings ,点击 Plugins,搜索 leetcode 插件 ,进行安装安装并启动力扣插件后,会发现右下角多了个力扣的按钮框,点击此按钮这样,一个 leetcode 插件就安装好了~整理不易,各位小伙伴记得点赞、评论,最后祝君在新的一年中好运爆棚~~~...

使用TortoiseGit,设置ssh方式连接git仓库。_weixin_34146410的博客-程序员ITS301

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...

推荐文章

热门文章

相关标签