只需一篇就让你详细了解 Java 中 so 文件的加载原理

/ 前言 /
 
无论是 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.javapublic static void load(String filename) {Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);}// java/lang/Runtime.javasynchronized 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.javapublic static void loadLibrary(String libname) {Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);}// java/lang/Runtime.javasynchronized 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() 具体见下章节 。


推荐阅读