日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

/ 前言 /

 

無(wú)論是 Android 開發(fā)者還是 JAVA 工程師應(yīng)該都有使用過(guò) JNI 開發(fā),但對(duì)于 JVM 如何加載 so、Android 系統(tǒng)如何加載 so,可能鮮有時(shí)間了解。

 

本文通過(guò)代碼、流程解釋,帶大家快速了解其加載原理,掃清困惑。

 

/ System#load() + loadLibrary() /

 

load()

 

System 提供的 load() 用于指定 so 的完整的路徑名且?guī)募缶Y并加載,等同于調(diào)用 Runtime 類提供的 load()。

 

If the filename argument, when stripped of any platform-specific library prefix, path, and file extension, indicates a library whose name is, for example, L, and a native library called L is statically linked with the VM, then the JNI_OnLoad_L function exported by the library is invoked rather than attempting to load a dynamic library.

 

Eg.

 

System.load("/sdcard/path/libA.so")

 

步驟簡(jiǎn)述:

 

  1. 通過(guò) Reflection 獲取調(diào)用來(lái)源的 Class 實(shí)例
  2. 接著調(diào)用 Runtime 的 load0() 實(shí)現(xiàn)

 

load0() 首先獲取系統(tǒng)的 SecurityManager。當(dāng) SecurityManager 存在的話檢查目標(biāo) so 文件的訪問(wèn)權(quán)限,權(quán)限不足的話打印拒絕信息、拋出 SecurityException ,如果 name 參數(shù)為空,拋出 NullPointerException。如果 so 文件名非絕對(duì)路徑的話,并不支持,并拋出 UnsatisfiedLinkError,message 為:

 

Expecting an absolute path of the library: xxx

 

針對(duì) so 文件的權(quán)限檢查和名稱檢查均通過(guò)的話,繼續(xù)調(diào)用 ClassLoader 的 loadLibrary() 實(shí)現(xiàn),需要留意的是絕對(duì)路徑參數(shù)為 true。

 

// java/lang/System.java
    public static void load(String filename) {
        Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
    }

// java/lang/Runtime.java
    synchronized void load0(Class<?> fromClass, String filename) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(filename);
        }
        if (!(new File(filename).isAbsolute())) {
            throw new UnsatisfiedLinkError(
                "Expecting an absolute path of the library: " + filename);
        }
        ClassLoader.loadLibrary(fromClass, filename, true);
    }

 

loadLibrary()

 

System 類提供的 loadLibrary() 用于指定 so 的名稱并加載,等同于調(diào)用 Runtime 類提供的 loadLibrary()。在 Android 平臺(tái)系統(tǒng)會(huì)自動(dòng)去系統(tǒng)目錄(/system/lib64/)、應(yīng)用 lib 目錄(/data/App/xxx/lib64/)下去找 libname 參數(shù)拼接了 lib 前綴的庫(kù)文件。

 

The libname argument must not contain any platform specific prefix, file extension or path.

If a native library called libname is statically linked with the VM, then the JNI_OnLoad_libname function exported by the library is invoked.

 

Eg.

 

System.loadLibrary("A")

 

步驟簡(jiǎn)述:

 

  1. 同樣通過(guò) Reflection 獲取調(diào)用來(lái)源的 Class 實(shí)例
  2. 接著調(diào)用 Runtime 的 loadLibrary0() 實(shí)現(xiàn)

 

loadLibrary0() 首先獲取系統(tǒng)的 SecurityManager,并檢查目標(biāo) so 文件的訪問(wèn)權(quán)限。權(quán)限不足或文件名為空的話和上面一樣拋出 Exception。確保 so 名稱不包含 /,反之拋出 UnsatisfiedLinkError,message 為:

 

Directory separator should not appear in library name: xxx

 

檢查通過(guò)后,同樣調(diào)用 ClassLoader 的 loadLibrary() 實(shí)現(xiàn)繼續(xù)下一步,只不過(guò)絕對(duì)路徑參數(shù)為 false。

 

// java/lang/System.java
    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }

// java/lang/Runtime.java
    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);
    }

 

/ ClassLoader#loadLibrary() /

 

上面的調(diào)用棧可以看到無(wú)論是 load() 還是 loadLibrary() 最終都是調(diào)用 ClassLoader 的 loadLibrary(),主要區(qū)別在于 name 參數(shù)是 lib 完整路徑、還是 lib 名稱,以及是否是絕對(duì)路徑參數(shù)。

 

1.首先通過(guò) getClassLoader() 獲得加載源所屬的 ClassLoader 實(shí)例

 

2.確保存放 libraries 路徑的字符串?dāng)?shù)組 sys_paths 不為空。尚且為空的話,調(diào)用 initializePath("java.library.path") 先初始化 usr 路徑字符串?dāng)?shù)組,再調(diào)用 initializePath("sun.boot.library.path") 初始化 system 路徑字符串?dāng)?shù)組。initializePath() 具體見下章節(jié)。

 

3.依據(jù)是否 isAbsolute 決定是否直接加載 library

 

name 是絕對(duì)路徑的話,直接創(chuàng)建 File 實(shí)例,調(diào)用 loadLibrary0(),繼續(xù)加載該文件。具體見下章節(jié)。檢查 loadLibrary0 的結(jié)果,true 即表示加載成功,結(jié)束;false 即表示加載失敗,拋出 UnsatisfiedLinkError

 

Can't load xxx

 

name 非絕對(duì)路徑并且獲取的 ClassLoader 存在的話,通過(guò) findLibrary() ,根據(jù) so 名稱獲得 lib 絕對(duì)路徑,并創(chuàng)建指向該路徑的 File 實(shí)例 libfile。并確保該文件的路徑是絕對(duì)路徑。反之,拋出 UnsatisfiedLinkError。

 

ClassLoader.findLibrary failed to return an absolute path: xxx

 

此后也是調(diào)用 loadLibrary0() 繼續(xù)加載該文件,并檢查 loadLibrary0 的結(jié)果,處理同上。

 

4.假使 ClassLoader 不存在,遍歷 system 路徑字符串?dāng)?shù)組的元素。

 

通過(guò) mapLibraryName() 分別將 lib name 映射到平臺(tái)關(guān)聯(lián)的 lib 完整名稱并返回,具體見下章節(jié)。創(chuàng)建當(dāng)前遍歷的 path 下 libfile 實(shí)例。調(diào)用 loadLibrary0() 繼續(xù)加載該文件,并檢查結(jié)果:

 

  • true 則直接結(jié)束
  • false 的話,通過(guò) mapAlternativeName() 獲取該 lib 可能存在的替代文件名,比如將后綴替換為 jnilib
    • 如果再度 map 后的 libfile 不為空,調(diào)用 loadLibrary0() 再度加載該文件并檢查結(jié)果,true 則直接結(jié)束;反之,進(jìn)入下一次循環(huán)

 

5.至此,如果仍未成功找到 library 文件,則在 ClassLoader 存在的情況下,到 usr 路徑字符串?dāng)?shù)組中查找。

  • 遍歷 usr 路徑字符串?dāng)?shù)組的元素
    • 后續(xù)邏輯和上述一致,只是 map 時(shí)候的前綴不同,是 usr_paths 的元素

 

6.最終進(jìn)行默認(rèn)處理,即拋出 UnsatisfiedLinkError,提示在 java.library.path propery 代表的路徑下也未找到 so 文件。

 

no xx in java.library.path

 

// java/lang/ClassLoader.java
    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(...);
                }
                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");
    }

 

/ ClassLoader#initializePath() /

 

從 System 中獲取對(duì)應(yīng) property 代表的 path 到數(shù)組中。

 

1.先調(diào)用 getProperty() 從 JVM 中取出配置的路徑,默認(rèn)的是 ""。

 

其中的 checkKey() 將檢查 key 名稱是否合法,null 的話拋出 NullPointerException:

 

key can't be null

 

如果為"",拋出 IllegalArgumentException:

 

key can't be empty

 

后面通過(guò) getSecurityManager() 獲取 SecurityManager 實(shí)例,檢查是否存在該 property 的訪問(wèn)權(quán)限。

 

2.如果允許引用路徑元素并且 存在的話,將路徑字符串的 char 取出進(jìn)行拼接、計(jì)算得到路徑字符串?dāng)?shù)組。

 

3.反之通過(guò) indexOf(/) 統(tǒng)計(jì) / 出現(xiàn)的次數(shù),并創(chuàng)建一個(gè) / 次數(shù) + 1 的數(shù)組。

 

4.遍歷該路徑字符串,通過(guò) substring() 將各 / 的中間 path 內(nèi)容提取到上述數(shù)組中。

 

5.最后返回得到的 path 數(shù)組。

 

// java/lang/ClassLoader.java
    private static String[] initializePath(String propname) {
        String ldpath = System.getProperty(propname, "");
        String ps = File.pathSeparator;
        ...

        i = ldpath.indexOf(ps);
        n = 0;
        while (i >= 0) {
            n++;
            i = ldpath.indexOf(ps, i + 1);
        }

        String[] paths = new String[n + 1];
        n = i = 0;
        j = ldpath.indexOf(ps);
        while (j >= 0) {
            if (j - i > 0) {
                paths[n++] = ldpath.substring(i, j);
            } else if (j - i == 0) {
                paths[n++] = ".";
            }
            i = j + 1;
            j = ldpath.indexOf(ps, i);
        }
        paths[n] = ldpath.substring(i, ldlen);
        return paths;
    }

 

/ ClassLoader#findLibrary() /

 

findLibrary() 將到 ClassLoader 中查找 lib,取決于各 JVM 的具體實(shí)現(xiàn)。比如可以看看 Android 上的實(shí)現(xiàn)。

 

  1. 到 DexPathList 的具體實(shí)現(xiàn)中調(diào)用
  2. 首先通過(guò) System 類的 mapLibraryName() 中獲得 mapping 后的 lib 全名,細(xì)節(jié)見下章節(jié)
  3. 遍歷存放 native lib 路徑元素?cái)?shù)組 nativeLibraryPathElements
  4. 逐個(gè)調(diào)用各元素的 findNativeLibrary() 實(shí)現(xiàn)去尋找
  5. 一經(jīng)找到立即返回,遍歷結(jié)束仍未發(fā)現(xiàn)的話返回 null

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// BaseDexClassLoader.java
   public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    public String findLibrary(String libraryName) {
        // 到 System 中獲得 mapping 后的 lib 全名
        String fileName = System.mapLibraryName(libraryName);

        // 到存放 native lib 路徑數(shù)組中遍歷
        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);

            // 一旦找到立即返回并結(jié)束,反之進(jìn)入下一次循環(huán)
            if (path != null) {
                return path;
            }
        }

        // 路徑中全找遍了,仍未找到則返回 null
        return null;
    }

 

System#mapLibraryName()

 

mapLibraryName() 的作用很簡(jiǎn)單,即將 lib 名稱 mapping 到完整格式的名稱,比如輸入 opencv 得到的是 libopencv.so。如果遇到名稱為空或者長(zhǎng)度超上限 240 的話,將拋出相應(yīng) Exception。

 

// java/lang/System.java
public static native String mapLibraryName(String libname);

 

其是 native 方法,具體實(shí)現(xiàn)位于 JDK Native Source Code 中,可在如下網(wǎng)站中看到,網(wǎng)站地址如下:

http://hg.openjdk.java.NET/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/native

 

// native/java/lang/System.c

#define JNI_LIB_PREFIX "lib"
#define JNI_LIB_SUFFIX ".so"

Java_java_lang_System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    // 定義最后名稱的 Sring 長(zhǎng)度變量
    int len;
    // 并獲取 lib 前綴、后綴的字符串常量的長(zhǎng)度
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);

    // 定義臨時(shí)的存放最后名稱的 char 數(shù)組
    jchar chars[256];
    // 如果 libname 參數(shù)為空,拋出 NPE
    if (libname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }
    // 獲取 libname 長(zhǎng)度
    len = (*env)->GetStringLength(env, libname);
    // 如果大于 240 的話拋出 IllegalArgumentException
    if (len > 240) {
        JNU_ThrowIllegalArgumentException(env, "name too long");
        return NULL;
    }

    // 將前綴 ”lib“ 的字符拷貝到臨時(shí)的 char 數(shù)組頭部
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    // 將 lib 名稱從字符串里拷貝到 char 數(shù)組的 “lib” 后面
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    // 更新名稱長(zhǎng)度為:前綴+ lib 名稱
    len += prefix_len;
    // 將后綴 ”.so“ 的字符拷貝到臨時(shí)的 char 數(shù)組里的 lib 名稱后
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    // 再次更新名稱長(zhǎng)度為:前綴+ lib 名稱 + 后綴
    len += suffix_len;

    // 從 char 數(shù)組里提取當(dāng)前長(zhǎng)度的復(fù)數(shù) char 成員到新創(chuàng)建的 String 對(duì)象中返回
    return (*env)->NewString(env, chars, len);
}

static void cpchars(jchar *dst, char *src, int n)
{
    int i;
    for (i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

 

邏輯很清晰,檢查 lib 名稱參數(shù)是否合法,之后便是將名稱分別加上前后綴到臨時(shí)字符數(shù)組中,最后轉(zhuǎn)為字符串返回。

 

nativeLibraryPathElements()

 

nativeLibraryPathElements 數(shù)組來(lái)源于獲取到的所有 native Library 目錄后轉(zhuǎn)換而來(lái)。

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    public DexPathList(ClassLoader definingContext, String librarySearchPath) {
        ...
        this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
    }

 

所有 native Library 目錄除了包含應(yīng)用自身的 library 目錄列表以外,還包括了系統(tǒng)的列表部分。

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    private List<File> getAllNativeLibraryDirectories() {
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        return allNativeLibraryDirectories;
    }

    /** List of application native library directories. */
    private final List<File> nativeLibraryDirectories;

    /** List of system native library directories. */
    private final List<File> systemNativeLibraryDirectories;

 

應(yīng)用自身的 library 目錄列表來(lái)自于 DexPathList 初始化時(shí)傳入的 librarySearchPath 參數(shù),splitPaths() 負(fù)責(zé)去該 path 下遍歷各級(jí)目錄得到對(duì)應(yīng)數(shù)組。

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    public DexPathList(ClassLoader definingContext, String librarySearchPath) {
        ...
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
    }

    private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
        List<File> result = new ArrayList<>();

        if (searchPath != null) {
            for (String path : searchPath.split(File.pathSeparator)) {
                if (directoriesOnly) {
                    try {
                        StructStat sb = Libcore.os.stat(path);
                        if (!S_ISDIR(sb.st_mode)) {
                            continue;
                        }
                    } catch (ErrnoException ignored) {
                        continue;
                    }
                }
                result.add(new File(path));
            }
        }

        return result;
    }

 

系統(tǒng)列表則來(lái)自于系統(tǒng)的 path 路徑,調(diào)用 splitPaths() 的第二個(gè)參數(shù)不同,促使其在分割的時(shí)候只處理目錄類型的部分,純文件的話跳過(guò)。

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    public DexPathList(ClassLoader definingContext, String librarySearchPath) {
        ...
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        ...
    }

 

拿到 path 文件列表之后就是調(diào)用 makePathElements 轉(zhuǎn)成對(duì)應(yīng)元素?cái)?shù)組。

 

  1. 按照列表長(zhǎng)度創(chuàng)建等長(zhǎng)的 Element 數(shù)組
  2. 遍歷 path 列表
  3. 如果 path 包含 "!/" 的話,將其拆分為 path 和 zipDir 兩部分,并創(chuàng)建 NativeLibraryElement 實(shí)例
  4. 反之如果是目錄的話,直接用 path 創(chuàng)建 NativeLibraryElement 實(shí)例,zipDir 參數(shù)則為空

 

// android/libcore/dalvik/src/main/java/dalvik/system/
// DexPathList.java
    private static NativeLibraryElement[] makePathElements(List<File> files) {
        NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
        int elementsPos = 0;
        for (File file : files) {
            String path = file.getPath();

            if (path.contains(zipSeparator)) {
                String split[] = path.split(zipSeparator, 2);
                File zip = new File(split[0]);
                String dir = split[1];
                elements[elementsPos++] = new NativeLibraryElement(zip, dir);
            } else if (file.isDirectory()) {
                // We support directories for looking up native libraries.
                elements[elementsPos++] = new NativeLibraryElement(file);
            }
        }
        if (elementsPos != elements.length) {
            elements = Arrays.copyOf(elements, elementsPos);
        }
        return elements;
    }

 

findNativeLibrary()

 

findNativeLibrary() 將先確保當(dāng) zip 目錄存在的情況下內(nèi)部處理 zip 的 ClassPathURLStreamHandler 實(shí)例執(zhí)行了創(chuàng)建。

 

  • 如果 zip 目錄不存在(一般情況下都是不存在的)直接判斷該路徑下 lib 文件是否可讀,YES 則返回 path/name、反之返回 null
  • zip 目錄存在并且 ClassPathURLStreamHandler 實(shí)例也創(chuàng)建完畢的話,檢查該 name 的 zip 文件的存在。并在 YES 的情況下,在 path 和 name 之間跟上 zip 目錄并返回,即:path/!/zipDir/name

 

// DexPathList.java
// android/.../libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
    private static final String zipSeparator = "!/";

    static class NativeLibraryElement {
        public String findNativeLibrary(String name) {
            // 確保 element 初始化完成
            maybeInit();

            if (zipDir == null) {
                // 如果 zip 目錄為空,則直接創(chuàng)建該 path 下該文件的 File 實(shí)例
                // 可讀的話則返回
                String entryPath = new File(path, name).getPath();
                if (IoUtils.canOpenReadOnly(entryPath)) {
                    return entryPath;
                }
            } else if (urlHandler != null) {
                // zip 目錄并且 urlHandler 都存在
                // 創(chuàng)建該 zip 目錄下 lib 文件的完整名稱
                String entryName = zipDir + '/' + name;
                // 如果該名稱的壓縮包是否存在的話
                if (urlHandler.isEntryStored(entryName)) {
                    // 返回:路徑/zip目錄/lib 名稱的結(jié)果出去
                    return path.getPath() + zipSeparator + entryName;
                }
            }

            return null;
        }

        // 主要是確保在 zipDir 不為空的情況下
        // 內(nèi)部處理 zip 的 urlHandler 實(shí)例已經(jīng)創(chuàng)建完畢
        public synchronized void maybeInit() {
            ...
        }
    }

 

/ ClassLoader#loadLibrary0() /

 

1.調(diào)用靜態(tài)內(nèi)部類 NativeLibrary 的 native 方法 findBuiltinLib() 檢查是否是內(nèi)置的動(dòng)態(tài)鏈接庫(kù),細(xì)節(jié)見如下章節(jié)。如果不是內(nèi)置的 library,通過(guò) AccessController 檢查該 library 文件是否存在。

 

  • 不存在則加載失敗并結(jié)束
  • 存在則到本 ClassLoader 已加載 library 的 nativeLibraries Vector 或系統(tǒng) class 的已加載 library Vector systemNativeLibraries 中查找是否加載過(guò)
    • 已加載過(guò)則結(jié)束
    • 反之繼續(xù)加載的任務(wù)

 

2.到所有 ClassLoader 已加載過(guò)的 library Vector loadedLibraryNames 里再次檢查是否加載過(guò),如果不存在的話,拋出 UnsatisfiedLinkError:

 

Native Library xxx already loaded in another classloader

 

3.到正在加載/卸載 library 的 nativeLibraryContext Stack 中檢查是否已經(jīng)處理中了

 

  • 存在并且 ClassLoader 來(lái)源匹配,則結(jié)束加載
  • 存在但 ClassLoader 來(lái)源不同,則拋出 UnsatisfiedLinkError:
    • Native Library xxx is being loaded in another classloader
  • 反之,繼續(xù)加載的任務(wù)

 

4.依據(jù) ClassLoader、library 名稱、是否內(nèi)置等信息,創(chuàng)建 NativeLibrary 實(shí)例并入 nativeLibraryContext 棧。

 

5.此后,交由 NativeLibrary load,細(xì)節(jié)亦見如下章節(jié),并在 load 后出棧。

 

6.最后根據(jù) load 的結(jié)果決定是否將加載記錄到對(duì)應(yīng)的 Vector 當(dāng)中。

 

// java/lang/ClassLoader.java
    private static boolean loadLibrary0(Class<?> fromClass, final File file) {
        // 獲取是否是內(nèi)置動(dòng)態(tài)鏈接庫(kù)
        String name = NativeLibrary.findBuiltinLib(file.getName());
        boolean isBuiltin = (name != null);
        if (!isBuiltin) {
            // 不是內(nèi)置的話,檢查文件是否存在
            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();
            // 檢查是否已經(jīng)加載過(guò)
            for (int i = 0; i < size; i++) {
                NativeLibrary lib = libs.elementAt(i);
                if (name.equals(lib.name)) {
                    return true;
                }
            }

            synchronized (loadedLibraryNames) {
                // 再次檢查所有 library 加載歷史中是否存在
                if (loadedLibraryNames.contains(name)) {
                    throw new UnsatisfiedLinkError(...);
                }
                int n = nativeLibraryContext.size();
                // 檢查是否已經(jīng)在加載中了
                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(...);
                        }
                    }
                }
                // 創(chuàng)建 NativeLibrary 實(shí)例繼續(xù)加載
                NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
                // 并在加載前后壓棧和出棧
                nativeLibraryContext.push(lib);
                try {
                    lib.load(name, isBuiltin);
                } finally {
                    nativeLibraryContext.pop();
                }
                // 加載成功的將該 library 名稱緩存到 vector 中
                if (lib.loaded) {
                    loadedLibraryNames.addElement(name);
                    libs.addElement(lib);
                    return true;
                }
                return false;
            }
        }
    }

 

findBuiltinLib()

 

  1. 首先一如既往地先檢查 library name 是否為空,為空則拋出 Error
    1. NULL filename for native library
  2. 將 string 類型的名稱轉(zhuǎn)為 char 指針,失敗的話拋出 OutOfMemoryError
  3. 檢查名稱長(zhǎng)度是否短于最起碼的 lib.so 幾位,失敗的話返回 NULL 結(jié)束
  4. 創(chuàng)建 library 名稱指針 libName 并分配內(nèi)存
  5. 從 char 指針提取 libxxx.so 中 xxx.so 部分到 libName 中
  6. 將 libName 中 .so 的 . 位置替換成
  7. 調(diào)用 findJniFunction() 依據(jù) handle 指針,library 名稱檢查該 library 的 JNI_OnLoad() 是否存在
    1. 存在則釋放 libName 內(nèi)存并返回該函數(shù)地址
    2. 反之,釋放內(nèi)存并返回 NULL 結(jié)束

 

// native/java/lang/ClassLoader.c
Java_java_lang_ClassLoader_00024NativeLibrary_findBuiltinLib
  (JNIEnv *env, jclass cls, jstring name)
{
    const char *cname;
    char *libName;
    ...
    // 檢查名稱是否為空
    if (name == NULL) {
        JNU_ThrowInternalError(env, "NULL filename for native library");
        return NULL;
    }

    procHandle = getProcessHandle();
    cname = JNU_GetStringPlatformChars(env, name, 0);
    // 檢查 char 名稱指針是否為空
    if (cname == NULL) {
        JNU_ThrowOutOfMemoryError(env, NULL);
        return NULL;
    }

    // 檢查名稱長(zhǎng)度
    len = strlen(cname);
    if (len <= (prefixLen+suffixLen)) {
        JNU_ReleaseStringPlatformChars(env, name, cname);
        return NULL;
    }
    // 提取 library 名稱(取出前后綴)
    libName = malloc(len + 1); //+1 for null if prefix+suffix == 0
    if (libName == NULL) {
        JNU_ReleaseStringPlatformChars(env, name, cname);
        JNU_ThrowOutOfMemoryError(env, NULL);
        return NULL;
    }
    if (len > prefixLen) {
        strcpy(libName, cname+prefixLen);
    }
    JNU_ReleaseStringPlatformChars(env, name, cname);
    libName[strlen(libName)-suffixLen] = '';

    // 檢查 JNI_OnLoad() 釋放存在
    ret = findJniFunction(env, procHandle, libName, JNI_TRUE);
    if (ret != NULL) {
        lib = JNU_NewStringPlatform(env, libName);
        free(libName);
        return lib;
    }
    free(libName);
    return NULL;
}

 

findJniFunction()

 

findJniFunction() 用于到 library 指針、已加載/卸載的 JNI 數(shù)組中查找該 library 名稱所對(duì)應(yīng)的 JNI_ONLOAD、JNI_ONUNLOAD 的函數(shù)地址。

 

// native/java/lang/ClassLoader.c
static 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 中獲取函數(shù)數(shù)組和長(zhǎng)度
    if (isLoad) {
        syms = onLoadSymbols;
        symsLen = sizeof(onLoadSymbols) / sizeof(char *);
    } else {
        // 反之,則到 JNI_ONUNLOAD_SYMBOLS 中獲取卸載函數(shù)數(shù)組和長(zhǎng)度
        syms = onUnloadSymbols;
        symsLen = sizeof(onUnloadSymbols) / sizeof(char *);
    }
    // 遍歷該數(shù)組,調(diào)用 JVM_FindLibraryEntry()
    // 逐個(gè)查找 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:
    // 如果沒(méi)有找到,默認(rèn)返回 NULL
    return entryName;
}

 

JVM_FindLibraryEntry()

 

JVM_FindLibraryEntry() 調(diào)用的是平臺(tái)相關(guān)的 dll_lookup(),依據(jù) library 指針和 function 名稱。

 

// vm/prims/jvm.cpp
JVM_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 內(nèi)的靜態(tài)內(nèi)部類,其代表著已加載 library 的實(shí)例,包含了該 library 的指針、所需的 JNI 版本、加載的 Class 來(lái)源、名稱、是否是內(nèi)置 library、是否加載過(guò)重要信息。以及核心的加載 load、卸載 unload native 實(shí)現(xiàn)。

 

// java/lang/ClassLoader.java
    static 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);
        ...
    }

 

本章節(jié)我們著重看下 load() 的關(guān)鍵實(shí)現(xiàn):

 

1.首先調(diào)用 initIDs() 初始化 ID 等基本數(shù)據(jù)

 

  • 如果 ClassLoader$NativeLibrary 內(nèi)部類、handle 等屬性有一不存在的話,返回 FALSE 并結(jié)束加載
  • 通過(guò)檢查的話初始化 procHandle 指針

 

2.其次通過(guò)
JNU_GetStringPlatformChars() 將 String 類型的 library 名稱轉(zhuǎn)為 char 類型,如果名稱為空的話結(jié)束加載

 

3.如果不是內(nèi)置的 so,需要調(diào)用 JVM_LoadLibrary() 加載得到指針(見下章節(jié)),反之沿用上述的 procHandle 指針即可

 

4.如果 so 指針存在的話,通過(guò) findJniFunction() 和指針參數(shù)獲取 JNI_OnLoad() 的地址。如果 JNI_OnLoad() 獲取成功,則調(diào)用它并得到該 so 要求的 jniVersion。反之設(shè)置為默認(rèn)值 0x00010001,即 JNI_VERSION_1_1,1.1。接著調(diào)用 JVM_IsSupportedJNIVersion() 檢查 JVM 是否支持該版本,調(diào)用的是 Threads 的 is_supported_jni_version_including_1_1()。如果不支持或者是內(nèi)置 so 同時(shí)版本低于 1.8,拋出 UnsatisfiedLinkError:

 

unsupported JNI version xxx required by yyy

 

反之表示加載成功。

 

5.反之,拋出異常 ExceptionOccurred。

 

// native/java/lang/ClassLoader.c
Java_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;
}

 

/ JVM_LoadLibrary() /

 

JVM_LoadLibrary() 是 JVM 這層加載 library 的最后一個(gè)實(shí)現(xiàn),具體步驟如下:

 

  1. 定義 1024 長(zhǎng)度的 char 數(shù)組和接收加載結(jié)果的指針
  2. 調(diào)用 dll_load() 加載 library,其細(xì)節(jié)見下章節(jié)
  3. 加載失敗的話,打印 library 名稱和錯(cuò)誤 message
  4. 同時(shí)拋出 UnsatisfiedLinkError
  5. 反之將加載結(jié)果返回

 

// vm/prims/jvm.cpp
JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name))
  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);
    Handle h_exception =
      Exceptions::new_exception(...);
    THROW_HANDLE_0(h_exception);
  }
  return load_result;
JVM_END

 

/ dll_load() /

 

dll_load() 的實(shí)現(xiàn)跟平臺(tái)相關(guān),比如 bsd 平臺(tái)就是調(diào)用標(biāo)準(zhǔn)庫(kù)的 dlopen(),而其最終的結(jié)果來(lái)自于 do_dlopen(),其將通過(guò) find_library() 得到 soinfo 實(shí)例,內(nèi)部將執(zhí)行 to_handle() 得到 library 的指針。

 

// bionic/libdl/libdl.cpp
void* dlopen(const char* filename, int flag) {
  const void* caller_addr = __builtin_return_address(0);
  return __loader_dlopen(filename, flag, caller_addr);
}

void* __loader_dlopen(const char* filename, int flags, const void* caller_addr) {
  return dlopen_ext(filename, flags, nullptr, caller_addr);
}

static void* dlopen_ext(...) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
  g_linker_logger.ResetState();
  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
  if (result == nullptr) {
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
    return nullptr;
  }
  return result;
}

void* do_dlopen(...) {
  ...
  if (si != nullptr) {
    void* handle = si->to_handle();
    si->call_constructors();
    failure_guard.Disable();
    return handle;
  }

  return nullptr;
}

 

/ JNI_OnLoad() /

 

JNI_OnLoad() 定義在 jni.h 中,當(dāng) library 被 JVM 加載時(shí)會(huì)回調(diào),該方法內(nèi)一般會(huì)通過(guò) registerNatives() 注冊(cè) native 方法并返回該 library 所需的 JNI 版本。該頭文件還定義了其他函數(shù)和常量,比如 JNI 1.1 等數(shù)值。

 

// jni.h
...
struct JNIEnv_ {
    ...
    jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,
                         jint nMethods) {
        return functions->RegisterNatives(this,clazz,methods,nMethods);
    }
    jint UnregisterNatives(jclass clazz) {
        return functions->UnregisterNatives(this,clazz);
    }
}
...
/* Defined by native libraries. */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);

#define JNI_VERSION_1_1 0x00010001
...

 

/ 結(jié)語(yǔ) /

 

總體流程可以歸納如下:

 

  1. System 類提供的 load() 加載 so 的完整的路徑名且?guī)募缶Y,等同于直接調(diào)用 Runtime 類提供的 load();loadLibrary() 用于加載指定 so 的名稱,等同于調(diào)用 Runtime 類提供的 loadLibrary()。
  2. 兩者都將通過(guò) SecurityManager 檢查 so 的訪問(wèn)權(quán)限以及名稱是否合法
  3. 之后調(diào)用 ClassLoader 類的 loadLibrary() 實(shí)現(xiàn),區(qū)別在于前者指定的是否是絕對(duì)路徑的 isAbsolute 參數(shù)是否為 true
  4. ClassLoader 首先需要通過(guò) System 提供的 getProperty() 獲取 JVM 配置的存放 usr、system library 路徑字符串?dāng)?shù)組
  5. 如果 library name 非絕對(duì)路徑,需要先調(diào)用 findLibrary() 獲取該 name 對(duì)應(yīng)的完整 so 文件,之后再調(diào)用 loadLibrary0() 繼續(xù)
  6. 當(dāng) ClassLoader 不存在,分別到 system、usr 字符串?dāng)?shù)組中查找該 so 是否存在
  7. loadLibrary0() 將調(diào)用 native 方法 findBuiltinLib() 檢查是否是內(nèi)置的動(dòng)態(tài)鏈接庫(kù),并到加載過(guò) vector、加載中 context 中查找是否已經(jīng)加載過(guò)、加載中
  8. 通過(guò)檢查的話調(diào)用 NativeLibrary 靜態(tài)內(nèi)部類繼續(xù),事實(shí)上是調(diào)用 ClassLoader.c 的 load()
  9. 其將調(diào)用 jvm.cpp 的 JVM_LoadLibrary() 進(jìn)行 so 的加載獲得指針
  10. 根據(jù) OS 的實(shí)現(xiàn),dll_load() 通過(guò) dlopen() 執(zhí)行 so 的打開和地址返回
  11. 最后通過(guò) findJniFunction() 獲取 JNI_OnLoad() 地址進(jìn)行 native 方法的注冊(cè)和所需 JNI 版本的收集。

原文鏈接:
https://mp.weixin.qq.com/s/HVQvjDhhUuCrkBuOP8PJZw

分享到:
標(biāo)簽:Java
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定