/ 前言 /
無(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)述:
- 通過(guò) Reflection 獲取調(diào)用來(lái)源的 Class 實(shí)例
- 接著調(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)述:
- 同樣通過(guò) Reflection 獲取調(diào)用來(lái)源的 Class 實(shí)例
- 接著調(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)。
- 到 DexPathList 的具體實(shí)現(xiàn)中調(diào)用
- 首先通過(guò) System 類的 mapLibraryName() 中獲得 mapping 后的 lib 全名,細(xì)節(jié)見下章節(jié)
- 遍歷存放 native lib 路徑元素?cái)?shù)組 nativeLibraryPathElements
- 逐個(gè)調(diào)用各元素的 findNativeLibrary() 實(shí)現(xiàn)去尋找
- 一經(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ù)組。
- 按照列表長(zhǎng)度創(chuàng)建等長(zhǎng)的 Element 數(shù)組
- 遍歷 path 列表
- 如果 path 包含 "!/" 的話,將其拆分為 path 和 zipDir 兩部分,并創(chuàng)建 NativeLibraryElement 實(shí)例
- 反之如果是目錄的話,直接用 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()
- 首先一如既往地先檢查 library name 是否為空,為空則拋出 Error
- NULL filename for native library
- 將 string 類型的名稱轉(zhuǎn)為 char 指針,失敗的話拋出 OutOfMemoryError
- 檢查名稱長(zhǎng)度是否短于最起碼的 lib.so 幾位,失敗的話返回 NULL 結(jié)束
- 創(chuàng)建 library 名稱指針 libName 并分配內(nèi)存
- 從 char 指針提取 libxxx.so 中 xxx.so 部分到 libName 中
- 將 libName 中 .so 的 . 位置替換成
- 調(diào)用 findJniFunction() 依據(jù) handle 指針,library 名稱檢查該 library 的 JNI_OnLoad() 是否存在
- 存在則釋放 libName 內(nèi)存并返回該函數(shù)地址
- 反之,釋放內(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),具體步驟如下:
- 定義 1024 長(zhǎng)度的 char 數(shù)組和接收加載結(jié)果的指針
- 調(diào)用 dll_load() 加載 library,其細(xì)節(jié)見下章節(jié)
- 加載失敗的話,打印 library 名稱和錯(cuò)誤 message
- 同時(shí)拋出 UnsatisfiedLinkError
- 反之將加載結(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ǔ) /
總體流程可以歸納如下:
- System 類提供的 load() 加載 so 的完整的路徑名且?guī)募缶Y,等同于直接調(diào)用 Runtime 類提供的 load();loadLibrary() 用于加載指定 so 的名稱,等同于調(diào)用 Runtime 類提供的 loadLibrary()。
- 兩者都將通過(guò) SecurityManager 檢查 so 的訪問(wèn)權(quán)限以及名稱是否合法
- 之后調(diào)用 ClassLoader 類的 loadLibrary() 實(shí)現(xiàn),區(qū)別在于前者指定的是否是絕對(duì)路徑的 isAbsolute 參數(shù)是否為 true
- ClassLoader 首先需要通過(guò) System 提供的 getProperty() 獲取 JVM 配置的存放 usr、system library 路徑字符串?dāng)?shù)組
- 如果 library name 非絕對(duì)路徑,需要先調(diào)用 findLibrary() 獲取該 name 對(duì)應(yīng)的完整 so 文件,之后再調(diào)用 loadLibrary0() 繼續(xù)
- 當(dāng) ClassLoader 不存在,分別到 system、usr 字符串?dāng)?shù)組中查找該 so 是否存在
- loadLibrary0() 將調(diào)用 native 方法 findBuiltinLib() 檢查是否是內(nèi)置的動(dòng)態(tài)鏈接庫(kù),并到加載過(guò) vector、加載中 context 中查找是否已經(jīng)加載過(guò)、加載中
- 通過(guò)檢查的話調(diào)用 NativeLibrary 靜態(tài)內(nèi)部類繼續(xù),事實(shí)上是調(diào)用 ClassLoader.c 的 load()
- 其將調(diào)用 jvm.cpp 的 JVM_LoadLibrary() 進(jìn)行 so 的加載獲得指針
- 根據(jù) OS 的實(shí)現(xiàn),dll_load() 通過(guò) dlopen() 執(zhí)行 so 的打開和地址返回
- 最后通過(guò) findJniFunction() 獲取 JNI_OnLoad() 地址進(jìn)行 native 方法的注冊(cè)和所需 JNI 版本的收集。
原文鏈接:
https://mp.weixin.qq.com/s/HVQvjDhhUuCrkBuOP8PJZw