亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

/ 前言 /

 

無論是 Android 開發者還是 JAVA 工程師應該都有使用過 JNI 開發,但對于 JVM 如何加載 so、Android 系統如何加載 so,可能鮮有時間了解。

 

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

 

/ System#load() + loadLibrary() /

 

load()

 

System 提供的 load() 用于指定 so 的完整的路徑名且帶文件后綴并加載,等同于調用 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")

 

步驟簡述:

 

  1. 通過 Reflection 獲取調用來源的 Class 實例
  2. 接著調用 Runtime 的 load0() 實現

 

load0() 首先獲取系統的 SecurityManager。當 SecurityManager 存在的話檢查目標 so 文件的訪問權限,權限不足的話打印拒絕信息、拋出 SecurityException ,如果 name 參數為空,拋出 NullPointerException。如果 so 文件名非絕對路徑的話,并不支持,并拋出 UnsatisfiedLinkError,message 為:

 

Expecting an absolute path of the library: xxx

 

針對 so 文件的權限檢查和名稱檢查均通過的話,繼續調用 ClassLoader 的 loadLibrary() 實現,需要留意的是絕對路徑參數為 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 的名稱并加載,等同于調用 Runtime 類提供的 loadLibrary()。在 Android 平臺系統會自動去系統目錄(/system/lib64/)、應用 lib 目錄(/data/App/xxx/lib64/)下去找 libname 參數拼接了 lib 前綴的庫文件。

 

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")

 

步驟簡述:

 

  1. 同樣通過 Reflection 獲取調用來源的 Class 實例
  2. 接著調用 Runtime 的 loadLibrary0() 實現

 

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

 

Directory separator should not appear in library name: xxx

 

檢查通過后,同樣調用 ClassLoader 的 loadLibrary() 實現繼續下一步,只不過絕對路徑參數為 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() /

 

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

 

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

 

2.確保存放 libraries 路徑的字符串數組 sys_paths 不為空。尚且為空的話,調用 initializePath("java.library.path") 先初始化 usr 路徑字符串數組,再調用 initializePath("sun.boot.library.path") 初始化 system 路徑字符串數組。initializePath() 具體見下章節。

 

3.依據是否 isAbsolute 決定是否直接加載 library

 

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

 

Can't load xxx

 

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

 

ClassLoader.findLibrary failed to return an absolute path: xxx

 

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

 

4.假使 ClassLoader 不存在,遍歷 system 路徑字符串數組的元素。

 

通過 mapLibraryName() 分別將 lib name 映射到平臺關聯的 lib 完整名稱并返回,具體見下章節。創建當前遍歷的 path 下 libfile 實例。調用 loadLibrary0() 繼續加載該文件,并檢查結果:

 

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

 

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

  • 遍歷 usr 路徑字符串數組的元素
    • 后續邏輯和上述一致,只是 map 時候的前綴不同,是 usr_paths 的元素

 

6.最終進行默認處理,即拋出 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 中獲取對應 property 代表的 path 到數組中。

 

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

 

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

 

key can't be null

 

如果為"",拋出 IllegalArgumentException:

 

key can't be empty

 

后面通過 getSecurityManager() 獲取 SecurityManager 實例,檢查是否存在該 property 的訪問權限。

 

2.如果允許引用路徑元素并且 存在的話,將路徑字符串的 char 取出進行拼接、計算得到路徑字符串數組。

 

3.反之通過 indexOf(/) 統計 / 出現的次數,并創建一個 / 次數 + 1 的數組。

 

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

 

5.最后返回得到的 path 數組。

 

// 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 的具體實現。比如可以看看 Android 上的實現。

 

  1. 到 DexPathList 的具體實現中調用
  2. 首先通過 System 類的 mapLibraryName() 中獲得 mapping 后的 lib 全名,細節見下章節
  3. 遍歷存放 native lib 路徑元素數組 nativeLibraryPathElements
  4. 逐個調用各元素的 findNativeLibrary() 實現去尋找
  5. 一經找到立即返回,遍歷結束仍未發現的話返回 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 路徑數組中遍歷
        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);

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

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

 

System#mapLibraryName()

 

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

 

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

 

其是 native 方法,具體實現位于 JDK Native Source Code 中,可在如下網站中看到,網站地址如下:

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 長度變量
    int len;
    // 并獲取 lib 前綴、后綴的字符串常量的長度
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);

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

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

    // 從 char 數組里提取當前長度的復數 char 成員到新創建的 String 對象中返回
    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 名稱參數是否合法,之后便是將名稱分別加上前后綴到臨時字符數組中,最后轉為字符串返回。

 

nativeLibraryPathElements()

 

nativeLibraryPathElements 數組來源于獲取到的所有 native Library 目錄后轉換而來。

 

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

 

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

 

// 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;

 

應用自身的 library 目錄列表來自于 DexPathList 初始化時傳入的 librarySearchPath 參數,splitPaths() 負責去該 path 下遍歷各級目錄得到對應數組。

 

// 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;
    }

 

系統列表則來自于系統的 path 路徑,調用 splitPaths() 的第二個參數不同,促使其在分割的時候只處理目錄類型的部分,純文件的話跳過。

 

// 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 文件列表之后就是調用 makePathElements 轉成對應元素數組。

 

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

 

// 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() 將先確保當 zip 目錄存在的情況下內部處理 zip 的 ClassPathURLStreamHandler 實例執行了創建。

 

  • 如果 zip 目錄不存在(一般情況下都是不存在的)直接判斷該路徑下 lib 文件是否可讀,YES 則返回 path/name、反之返回 null
  • zip 目錄存在并且 ClassPathURLStreamHandler 實例也創建完畢的話,檢查該 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 目錄為空,則直接創建該 path 下該文件的 File 實例
                // 可讀的話則返回
                String entryPath = new File(path, name).getPath();
                if (IoUtils.canOpenReadOnly(entryPath)) {
                    return entryPath;
                }
            } else if (urlHandler != null) {
                // zip 目錄并且 urlHandler 都存在
                // 創建該 zip 目錄下 lib 文件的完整名稱
                String entryName = zipDir + '/' + name;
                // 如果該名稱的壓縮包是否存在的話
                if (urlHandler.isEntryStored(entryName)) {
                    // 返回:路徑/zip目錄/lib 名稱的結果出去
                    return path.getPath() + zipSeparator + entryName;
                }
            }

            return null;
        }

        // 主要是確保在 zipDir 不為空的情況下
        // 內部處理 zip 的 urlHandler 實例已經創建完畢
        public synchronized void maybeInit() {
            ...
        }
    }

 

/ ClassLoader#loadLibrary0() /

 

1.調用靜態內部類 NativeLibrary 的 native 方法 findBuiltinLib() 檢查是否是內置的動態鏈接庫,細節見如下章節。如果不是內置的 library,通過 AccessController 檢查該 library 文件是否存在。

 

  • 不存在則加載失敗并結束
  • 存在則到本 ClassLoader 已加載 library 的 nativeLibraries Vector 或系統 class 的已加載 library Vector systemNativeLibraries 中查找是否加載過
    • 已加載過則結束
    • 反之繼續加載的任務

 

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

 

Native Library xxx already loaded in another classloader

 

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

 

  • 存在并且 ClassLoader 來源匹配,則結束加載
  • 存在但 ClassLoader 來源不同,則拋出 UnsatisfiedLinkError:
    • Native Library xxx is being loaded in another classloader
  • 反之,繼續加載的任務

 

4.依據 ClassLoader、library 名稱、是否內置等信息,創建 NativeLibrary 實例并入 nativeLibraryContext 棧。

 

5.此后,交由 NativeLibrary load,細節亦見如下章節,并在 load 后出棧。

 

6.最后根據 load 的結果決定是否將加載記錄到對應的 Vector 當中。

 

// java/lang/ClassLoader.java
    private static boolean loadLibrary0(Class<?> fromClass, final File file) {
        // 獲取是否是內置動態鏈接庫
        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) {
                // 再次檢查所有 library 加載歷史中是否存在
                if (loadedLibraryNames.contains(name)) {
                    throw new 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(...);
                        }
                    }
                }
                // 創建 NativeLibrary 實例繼續加載
                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 類型的名稱轉為 char 指針,失敗的話拋出 OutOfMemoryError
  3. 檢查名稱長度是否短于最起碼的 lib.so 幾位,失敗的話返回 NULL 結束
  4. 創建 library 名稱指針 libName 并分配內存
  5. 從 char 指針提取 libxxx.so 中 xxx.so 部分到 libName 中
  6. 將 libName 中 .so 的 . 位置替換成
  7. 調用 findJniFunction() 依據 handle 指針,library 名稱檢查該 library 的 JNI_OnLoad() 是否存在
    1. 存在則釋放 libName 內存并返回該函數地址
    2. 反之,釋放內存并返回 NULL 結束

 

// 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;
    }

    // 檢查名稱長度
    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 數組中查找該 library 名稱所對應的 JNI_ONLOAD、JNI_ONUNLOAD 的函數地址。

 

// 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 中獲取函數數組和長度
    if (isLoad) {
        syms = onLoadSymbols;
        symsLen = sizeof(onLoadSymbols) / sizeof(char *);
    } else {
        // 反之,則到 JNI_ONUNLOAD_SYMBOLS 中獲取卸載函數數組和長度
        syms = onUnloadSymbols;
        symsLen = sizeof(onUnloadSymbols) / sizeof(char *);
    }
    // 遍歷該數組,調用 JVM_FindLibraryEntry()
    // 逐個查找 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:
    // 如果沒有找到,默認返回 NULL
    return entryName;
}

 

JVM_FindLibraryEntry()

 

JVM_FindLibraryEntry() 調用的是平臺相關的 dll_lookup(),依據 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 內的靜態內部類,其代表著已加載 library 的實例,包含了該 library 的指針、所需的 JNI 版本、加載的 Class 來源、名稱、是否是內置 library、是否加載過重要信息。以及核心的加載 load、卸載 unload native 實現。

 

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

 

本章節我們著重看下 load() 的關鍵實現:

 

1.首先調用 initIDs() 初始化 ID 等基本數據

 

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

 

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

 

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

 

4.如果 so 指針存在的話,通過 findJniFunction() 和指針參數獲取 JNI_OnLoad() 的地址。如果 JNI_OnLoad() 獲取成功,則調用它并得到該 so 要求的 jniVersion。反之設置為默認值 0x00010001,即 JNI_VERSION_1_1,1.1。接著調用 JVM_IsSupportedJNIVersion() 檢查 JVM 是否支持該版本,調用的是 Threads 的 is_supported_jni_version_including_1_1()。如果不支持或者是內置 so 同時版本低于 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 的最后一個實現,具體步驟如下:

 

  1. 定義 1024 長度的 char 數組和接收加載結果的指針
  2. 調用 dll_load() 加載 library,其細節見下章節
  3. 加載失敗的話,打印 library 名稱和錯誤 message
  4. 同時拋出 UnsatisfiedLinkError
  5. 反之將加載結果返回

 

// 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() 的實現跟平臺相關,比如 bsd 平臺就是調用標準庫的 dlopen(),而其最終的結果來自于 do_dlopen(),其將通過 find_library() 得到 soinfo 實例,內部將執行 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 中,當 library 被 JVM 加載時會回調,該方法內一般會通過 registerNatives() 注冊 native 方法并返回該 library 所需的 JNI 版本。該頭文件還定義了其他函數和常量,比如 JNI 1.1 等數值。

 

// 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
...

 

/ 結語 /

 

總體流程可以歸納如下:

 

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

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

分享到:
標簽:Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定