下面我们结合源码详细探讨下JNI调用的库文件是如何加载的,为啥HelloWorld.so必须被命名成libHelloWorld.so,JNI_OnLoad方法是在什么时候回调的,返回的版本号有啥用?先看下总体的流程图
Java源码解析
System.loadLibrary和System.load方法
System.loadLibrary(String)方法用来加载动态链接库的,String参数是指定动态链接库的模块名的而非真实的文件名的。System还有另外一个load(String)方法,也是用来加载动态链接库的,不过String参数是库文件的绝对路径名,比如上述示例中的System.loadLibrary("HelloWorld") 可以替换成System.load("/home/openjdk/cppTest/HelloWorld.so")。两者的区别在于前者的库文件位置是配置化的,更灵活;而后者是代码写死的,适用于测试场景,生产不建议使用。两者的源码如下:
public static void load(String filename) {
Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
查看两者的注释,load方法要求fileName必须是绝对路径,JVM解析时会去除路径部分和文件后缀得到文件名,利用文件名生成该动态链接库的模块名,比如文件名是HelloWorld,JVM中用JNI_OnLoad_HelloWorld来表示这个模块;loadLibrary方法则直接使用JNI_OnLoad_libname作为模块名。
从类图我们可以知道接下来会调用Runtime类的loadLibrary0方法,在这个方法里面会做两件事:
- 通过loader.findLibrary(libraryName)找到对应库的全路径
- 通过doLoad(filename, loader)加载库文件
Runtime.loadLibrary和Runtime.load方法
Runtime的这两个方法的源码如下:
public void load(String filename) {
load0(Reflection.getCallerClass(), filename);
}
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);
}
public void loadLibrary(String libname) {
loadLibrary0(Reflection.getCallerClass(), libname);
}
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);
}
Class
Loader.loadLibrary(fromClass, libname, false);
}
可以看到最终都是调用ClassLoader.loadLibrary方法完成动态链接库文件的加载。
ClassLoader.loadLibrary方法
static void loadLibrary(Class fromClass, String name,
boolean isAbsolute) {
//fromClass是一开始调用System的类,getClassLoader一般返回AppClassLoader实例,
//只有通过启动类加载器加载的类如String,getClassLoader会返回null
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader();
//usr_paths和sys_paths是ClassLoader的两个静态属性,如果为空则初始化
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);
}
//如果loader不为空,尝试通过findLibrary查找指定模块的绝对路径
if (loader != null) {
//ClassLoader的实现默认返回空,只有ExtClassLoader改写了该方法
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
File libfile = new File(libfilename);
//校验是否是绝对路径,如果不为null则必须是绝对路径
if (!libfile.isAbsolute()) {
throw new UnsatisfiedLinkError(
"ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
}
if (loadLibrary0(fromClass, libfile)) {
return;
}
throw new UnsatisfiedLinkError("Can't load " + libfilename);
}
}
//无论loader是否为空
//遍历sys_paths下的子路径,查找是否存在目标文件
for (int i = 0 ; i < sys_paths.length ; i++) {
//获取映射后的子路径下的文件名,mapLibraryName是本地方法
File libfile = new File(sys_paths[i], System.mapLibraryName(name));
//尝试加载目标文件,如果存在则返回
if (loadLibrary0(fromClass, libfile)) {
return;
}
//mapAlternativeName默认返回null
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
//如果loader不为空,通常会走到此逻辑
if (loader != null) {
//遍历usr_paths下的所有子路径
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;
}
}
}
//没有找到,加载失败,抛出异常
throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}
protected String findLibrary(String libname) {
return null;
}
findLibrary被改写的子类如下:
System.mapLibraryName是一个本地方法,表示将库名映射成特定平台下的库文件名,定义如下:
ClassLoader.loadLibrary0方法
private static boolean loadLibrary0(Class fromClass, final File file) {
//检查是否是内置的动态链接库,findBuiltinLib是本地方法
String name = findBuiltinLib(file.getName());
boolean isBuiltin = (name != null);
//如果不是
if (!isBuiltin) {
boolean exists = AccessController.doPrivileged(
new PrivilegedAction