前语
无论是 Android 开发者仍是 Java 工程师应该都有运用过 JNI 开发,但对于 JVM 怎么加载 so、Android 体系怎么加载 so,或许鲜有时间了解。
本文经过代码、流程解释,带大家快速了解其加载原理,扫清困惑。
1. System#load() + loadLibrary()
1.1 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")
进程简述:
-
经过 Reflection 获取调用来历的 Class 实例
-
接着调用 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);
}
1.2 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")
进程简述:
-
同样经过 Reflection 获取调用来历的 Class 实例
-
接着调用 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);
}
2. ClassLoader#loadLibrary()
上面的调用栈能够看到无论是 load()
仍是 loadLibrary()
终究都是调用 ClassLoader
的 loadLibrary()
,首要差异在于 name 参数是 lib 完好途径、仍是 lib 称号,以及是否是绝对途径参数。
-
首要经过
getClassLoader()
取得加载源所属的 ClassLoader 实例 -
保证寄存 libraries 途径的字符串数组
sys_paths
不为空- 尚且为空的话,调用 initializePath(“java.library.path”) 先初始化
usr
途径字符串数组,再调用 initializePath(“sun.boot.library.path”) 初始化system
途径字符串数组。initializePath()
详细见下章节
- 尚且为空的话,调用 initializePath(“java.library.path”) 先初始化
-
依据是否
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 的成果,处理同上
-
-
-
假使 ClassLoader 不存在:遍历 system 途径字符串数组的元素,
-
经过
mapLibraryName()
别离将 lib name 映射到平台相关的 lib 完好称号并回来,详细见下章节 -
创立当时遍历的 path 下 libfile 实例
-
调用 loadLibrary0() 持续加载该文件,并查看成果:
-
true 则直接完毕
-
false 的话,经过 mapAlternativeName() 获取该 lib 或许存在的替代文件名,比方将后缀替换为
jnilib
- 假如再度 map 后的 libfile 不为空,调用 loadLibrary0() 再度加载该文件并查看成果,true 则直接完毕;反之,进入下一次循环
-
-
-
至此,假如仍未成功找到 library 文件,则在 ClassLoader 存在的情况下,到 usr 途径字符串数组中查找
- 遍历 usr 途径字符串数组的元素
- 后续逻辑和上述一致,仅仅 map 时分的前缀不同,是 usr_paths 的元素
- 遍历 usr 途径字符串数组的元素
-
终究进行默许处理,即抛出
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");
}
3. ClassLoader#initializePath()
从 System
中获取对应 property 代表的 path 到数组中。
-
先调用
getProperty()
从 JVM 中取出配置的途径,默许的是 “”-
其中的
checkKey()
将查看 key 称号是否合法,null
的话抛出NullPointerException
key can’t be null
假如为
""
,抛出IllegalArgumentException
key can’t be empty
-
后面经过
getSecurityManager()
获取SecurityManager
实例,查看是否存在该 property 的拜访权限
-
-
假如允许引证途径元素而且 \ 存在的话,将途径字符串的 char 取出进行拼接、计算得到途径字符串数组
-
反之经过 indexOf(/) 统计 / 出现的次数,并创立一个 / 次数 + 1 的数组
-
遍历该途径字符串,经过 substring() 将各 / 的中间 path 内容提取到上述数组中
-
终究回来得到的 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;
}
4. ClassLoader#findLibrary()
findLibrary() 将到 ClassLoader
中查找 lib,取决于各 JVM 的详细完结。比方能够看看 Android 上的完结。
- 到
DexPathList
的详细完结中调用 - 首要经过 System 类的
mapLibraryName()
中取得 mapping 后的 lib 全名,细节见下章节 - 遍历寄存 native lib 途径元素数组
nativeLibraryPathElements
- 逐一调用各元素的
findNativeLibrary()
完结去寻觅 - 一经找到立即回来,遍历完毕仍未发现的话回来 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;
}
4.1 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 中,可在如下网站中看到:
- hg.openjdk.java.net/jdk8/jdk8/j…
// 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 称号参数是否合法,之后便是将称号别离加上前后缀到暂时字符数组中,终究转为字符串回来。
4.2 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
转成对应元素数组。
- 按照列表长度创立等长的
Element
数组 - 遍历 path 列表
- 假如 path 包含 “!/” 的话,将其拆分为 path 和 zipDir 两部分,并创立
NativeLibraryElement
实例 - 反之,假如是目录的话,直接用 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;
}
4.3 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() {
...
}
}
5. ClassLoader#loadLibrary0()
-
调用静态内部类
NativeLibrary
的 native 办法findBuiltinLib()
查看是否是内置的动态链接库,细节见如下章节- 假如不是内置的 library,经过
AccessController
查看该 library 文件是否存在- 不存在则加载失利并完毕
- 存在则到本 ClassLoader 已加载 library 的 nativeLibraries Vector 或体系 class 的已加载 library Vector systemNativeLibraries 中查找是否加载过
- 已加载过则完毕
- 反之,持续加载的使命
- 假如不是内置的 library,经过
-
到一切 ClassLoader 已加载过的 library Vector
loadedLibraryNames
里再次查看是否加载过,假如不存在的话,抛出 UnsatisfiedLinkError:Native Library xxx already loaded in another classloader
-
到正在加载/卸载 library 的
nativeLibraryContext
Stack 中查看是否已经处理中了-
存在而且 ClassLoader 来历匹配,则完毕加载
-
存在但 ClassLoader 来历不同,则抛出 UnsatisfiedLinkError:
Native Library xxx is being loaded in another classloader
-
反之,持续加载的使命
-
-
依据 ClassLoader、library 称号、是否内置等信息,创立 NativeLibrary 实例并入 nativeLibraryContext 栈
-
尔后,交由 NativeLibrary load,细节亦见如下章节,并在 load 后出栈
-
终究依据 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;
}
}
}
5.1 findBuiltinLib()
-
首要一如既往地先查看 library name 是否为空,为空则抛出 Error
NULL filename for native library
-
将 string 类型的称号转为 char 指针,失利的话抛出
OutOfMemoryError
-
查看称号长度是否短于最起码的 lib.so 几位,失利的话回来 NULL 完毕
-
创立 library 称号指针 libName 并分配内存
-
从 char 指针提取 libxxx.so 中 xxx.so 部分到 libName 中
-
将 libName 中 .so 的 . 位置替换成 \0
-
调用 findJniFunction() 依据 handle 指针,library 称号查看该 library 的 JNI_OnLoad() 是否存在
- 存在则开释 libName 内存并回来该函数地址
- 反之,开释内存并回来 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] = '\0';
// 查看 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;
}
5.2 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 + '_' + '\0'
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;
}
5.3 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
6. 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() 的要害完结:
-
首要调用
initIDs()
初始化 ID 等根本数据- 假如 ClassLoader$NativeLibrary 内部类、handle 等特点有一不存在的话,回来 FALSE 并完毕加载
- 经过查看的话初始化
procHandle
指针
-
其次经过
JNU_GetStringPlatformChars()
将 String 类型的 library 称号转为 char 类型,假如称号为空的话完毕加载 -
假如不是内置的 so,需求调用
JVM_LoadLibrary()
加载得到指针(见下章节),反之沿袭上述的 procHandle 指针即可 -
假如 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
-
反之表明加载成功
-
-
-
反之,抛出反常
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;
}
7. JVM_LoadLibrary()
JVM_LoadLibrary() 是 JVM 这层加载 library 的终究一个完结,详细进程如下:
- 界说 1024 长度的 char 数组和接收加载成果的指针
- 调用
dll_load()
加载 library,其细节见下章节 - 加载失利的话,打印 library 称号和错误 message
- 一起抛出
UnsatisfiedLinkError
- 反之将加载成果回来
// 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
8. 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;
}
9. 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
...
结语
整体流程能够归纳如下:
-
System 类供给的
load()
加载 so 的完好的途径名且带文件后缀,等同于直接调用 Runtime 类供给的 load();loadLibrary()
用于加载指定 so 的称号,等同于调用 Runtime 类供给的 loadLibrary()。- 两者都将经过 SecurityManager 查看 so 的拜访权限以及称号是否合法
- 之后调用 ClassLoader 类的
loadLibrary()
完结,差异在于前者指定的是否是绝对途径的 isAbsolute 参数是否为 true -
ClassLoader 首要需求经过 System 供给的
getProperty()
获取 JVM 配置的寄存 usr、system library 途径字符串数组 - 假如 library name 非绝对途径,需求先调用
findLibrary()
获取该 name 对应的完好 so 文件,之后再调用loadLibrary0()
持续- 当 ClassLoader 不存在,别离到 system、usr 字符串数组中查找该 so 是否存在
-
loadLibrary0()
将调用 native 办法findBuiltinLib()
查看是否是内置的动态链接库,并到加载过 vector、加载中 context 中查找是否已经加载过、加载中 - 经过查看的话调用 NativeLibrary 静态内部类持续,事实上是调用 ClassLoader.c 的
load()
- 其将调用 jvm.cpp 的
JVM_LoadLibrary()
进行 so 的加载取得指针 - 依据 OS 的完结,
dll_load()
经过dlopen()
履行 so 的打开和地址回来 - 终究经过
findJniFunction()
获取JNI_OnLoad()
地址进行 native 办法的注册和所需 JNI 版本的搜集。
源码地址
- BaseDexClassLoader.java
- System.c
- ClassLoader.c
- jvm.cpp
- libdl.cpp
- jni.h
参阅资料
写作本文的时分参阅了很多文章的内容,也有部分内容论述了 JNI 原理以外的东西,大家能够结合着一起看看。
- Hotspot JNI库文件加载源码解析
- JNI开发之JNI原理
- Android JNI:深入分析安卓JNI原理
- JVM的启动进程
- 深入理解JNI
- JNI/NDK入门指南之JavaVM和JNIEnv
- Android 源码中的 JNI,到底是怎么运用的?
- Java System.load() 与 System.loadLibrary() 差异解析
- so加载 – Linker跟NameSpace知识
- Android NDK开发:JNI基础篇
- Android NDK开发:JNI实战篇
- JNI 从入门到实践,万字爆肝详解!
- 怎么查看JVM的源码
- Where to find source code for java.lang native methods?
- Java native method source code