在看Hotspot实战的过程中,发现介绍HotSpot的Prims模块的时候,它有一个子模块为JNI,用来处理Java与C/C++代码的交互。这篇文章用来记录学习和研究JNI的过程。
JNI - 概述
假如我们写了一段java代码:class HelloWorld {
public native void TestMethod();
/* 现在java中定义出来Native方法,可以用javah来生成统一的C/C++的.h文件模板;
实现了c/c++代码后,调用的时候,还是需要加载TestMethod对应的.dll库的;
这个就是常说的,创建一个 JNI-API
*/
static {
System.load("F://HelloWorld");
/* load:加载一个已经有的库文件HelloWorld.dll,load方法需要传绝对路径;
java会按照给定的目录加载对应的HelloWorld.ddl;
介绍 加载动态库 的时候会分析load和LoadLibrary的实现;
*/
}
public static void main(String[] args) {
new HelloWorld().TestMethod(); //调用Native文件
}
}
(1) 在HelloWorld类中,定义了一个native方法:TestMethod
(2) 首先编译HelloWorld(javac),然后使用命令:javah HelloWorld,会在执行命令的路径下生成一个HelloWorld.h的C/C++头文件;
(3) 创建一个HelloWorld.cpp的工程,并且引用HelloWorld.h,实现对应的native方法,最后组建成一个HelloWorld.ddl
当组建成一个ddl动态库,并且将其放在F://HelloWorld路径下,则可以使用load加载到了;
JNI - Javah生成的头文件
用javah生成一个具有标准格式的*.h头文件,本小节主要是分析一下通用格式头文件的内容,下面就是上一小节中的java代码通过javac生成的HelloWorld.h文件的内容;/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: TestMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_TestMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
从上面的代码中,我们着重关注的几个点如下:
1、HelloWorld.h中首先要引入<jni.h>,比如HotSpot虚拟机代码 - src\share\vm\prims\prims\jni.h
2、关于TestMethod方法在HelloWorld.h中声明的方法名
方法名是有统一格式的:Java_ClassName_MethodName;
而且注意,ClassName是包括package的,比如package为:com.test,那么头文件中对应的native方法名为:Java_com_test_HelloWorld_TestMethod
另外说一下,在后续分析System.load和loadLibrary代码时,里面最终进行加载动态库的也是native方法load,看load的源码,可以看出来,命名方法也是这个格式的。
3、方法参数:JNIEnv * JNIEnv 指代了Java本地接口环境(Java Native Interface Environment)
4、方法的返回值,例子中的是void,但是实际上在jni.h中定义了许多类型,比如jint等等;
JNI - jni.h文件代码分析
JNI - jni.h文件代码分析 - 中间数据结构定义
Java数据类型分为两种:基础数据类型、引用类型。 基础数据类型又分为:数值类型(整数类型,如int、char、long、byte等;浮点类型:double、float)、boolean类型(true、false,在虚拟机规范中明确了用int类型1表示true,0 表示false)、returnAddress类型(Java中没有定义,主要用于虚拟机中表示指向字节码的指针)。Java这些数据,在Java代码中写完后,和C++是不共通的,比如我定义了一个native方法:
public native static boolean testMethod2();
返回值为boolean类型,然而在C++中没有boolean类型,这个时候,就需要定义一个Java和C++之间这种数据结构的中间定义,见<jni.h>中的定义:
#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize; //将jint又进行typedef为jsize
// -------- 介绍jni_md.h的分割线 --------------------------------------------- //
/* 在jni.h中#include jni_md.h,jni.md.h文件主要根据cpu来进行文件的加载 */
#ifdef TARGET_ARCH_x86
# include "jni_x86.h"
/* 在jni_x86.h 文件中,定义了JAVA中int、byte、long在C中的对应数据类型*/
#define JNICALL
typedef int jint;
typedef signed char jbyte;
typedef long jlong; /* 或 */ typedef long long jlong; //根据cpu不同的范围,x86_64架构的CPU,long就是对应jlong
// -------- 分割线结束,回到jni.h文件 --------------------------------------------- //
/* 上述是基础数据类型 - 整数型的数据映射,下面是数组的 */
#ifdef __cplusplus
class _jobject {};
class _jclass : public _jobject {}; //都继承于_jobject
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;
#else
struct z;
typedef struct _jobject *jobject; /* 可以看到jobject是一个指针,指向_jobject,发现就是c++中声明的,C用的也和C++一样的 */
typedef jobject jclass; /* jobject可以指向多种子类型,jclass这些都继承于_jobject */
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
#endif
/* 很明显,如果是C++的,就是在#ifdef _cplusplus 中声明的映射,C则是在#else中声明的映射 */
Java - JNI - C/C++ 基础数据类型对应表如下:
Java | JVM<jni.h>定义的中间类型 | C/C++类型 | 备注 |
---|---|---|---|
byte | jbyte | signed char | 定义于jni_x86.h中 |
int | jint/jsize | int | 定义于jni_x86.h中,在<jni.h>中又将jint起名为:jsize |
long | jlong | long | 定义于jni_x86.h中 |
char | jchar | unsigned short | 定义于jni.h中 |
boolead | jboolean | unsigned char | 定义于jni.h中 |
short | jshort | short | 定义于jni.h中 |
float | jfloat | folat | 定义于jni.h中 |
double | jdouble | double | 定义于jni.h中 |
除了基础数据类型,肯定还有对象类型,从代码中的定义来看,所有的对象类型都继承于_jobject,Java - JNI - C/C++ 对象类型对应表如下:
JNI - jni.h文件代码分析 - JNIEnv与JavaVM结构定义
JavaVM:是Java虚拟机在JNI层面的代表,启动一个JVM只会生成一个JavaVM,也就是说,当有多个线程执行JNI时,也是公用的同一个JavaVM。
JNIEnv:以线程为单位,表示Java调用Native的环境,下面的代码分析中可以看到,一个JNIEnv基本包含了JNI功能可能用到的全部 JNI 方法。
<jin.h>中对于JavaVM的定义:
struct JNIInvokeInterface_;
struct JavaVM_;
#ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif
struct JavaVM_ {
const struct JNIInvokeInterface_ *functions;
#ifdef __cplusplus
jint DestroyJavaVM() {
return functions->DestroyJavaVM(this);
}
jint AttachCurrentThread(void **penv, void *args) {
return functions->AttachCurrentThread(this, penv, args);
}
jint DetachCurrentThread() {
return functions->DetachCurrentThread(this);
}
jint GetEnv(void **penv, jint version) {
return functions->GetEnv(this, penv, version);
}
jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
return functions->AttachCurrentThreadAsDaemon(this, penv, args);
}
#endif
};
<jin.h>中对于JNIEnv的定义:
/*
* JNI Native Method Interface.
*/
struct JNINativeInterface_;
struct JNIEnv_;
//此处针对C和C++做一个预先判定和typedef定义
#ifdef __cplusplus //如果是C++
typedef JNIEnv_ JNIEnv; //JNIEnv就是JNIEnv_ struct
#else
typedef const struct JNINativeInterface_ *JNIEnv; //如果是C,则JNIEnv是一个指向JNINativeInterface_结构实现的指针
#endif
/* 根据这个#ifdef就可以理解,为什么网上有的文章说JNIEnv的作用实际上是区分了C和C++;*/
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env); //之前提到过的返回值类型jint、jclass等等
jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
jclass (JNICALL *FindClass)
(JNIEnv *env, const char *name);
jmethodID (JNICALL *FromReflectedMethod)
(JNIEnv *env, jobject method);
jfieldID (JNICALL *FromReflectedField)
(JNIEnv *env, jobject field);
jobject (JNICALL *ToReflectedMethod)
(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
jclass (JNICALL *GetSuperclass)
(JNIEnv *env, jclass sub);
jboolean (JNICALL *IsAssignableFrom)
(JNIEnv *env, jclass sub, jclass sup);
/*太多了,省略粘贴,有兴趣可以下个虚拟机的源码找打自己看一下*/
jweak (JNICALL *NewWeakGlobalRef)
(JNIEnv *env, jobject obj);
void (JNICALL *DeleteWeakGlobalRef)
(JNIEnv *env, jweak ref);
jboolean (JNICALL *ExceptionCheck)
(JNIEnv *env);
jobject (JNICALL *NewDirectByteBuffer)
(JNIEnv* env, void* address, jlong capacity);
void* (JNICALL *GetDirectBufferAddress)
(JNIEnv* env, jobject buf);
jlong (JNICALL *GetDirectBufferCapacity)
(JNIEnv* env, jobject buf);
/* New JNI 1.6 Features */
jobjectRefType (JNICALL *GetObjectRefType)
(JNIEnv* env, jobject obj);
};
从JavaVM和JNIEnv的分析来看,除了二者一个是全局,一个线程独立这个区别外,基础的作用都是一致的,都是为了在程序员实现native方法时提供一些已经实现的方法,比如JavaVM中有GetEnv方法来获取当前线程的JNIEnv,JNIEnv也有方法GetJavaVm来获取全局变量JavaVM;
补充:《HotSpot实战》中介绍Prims模块时说过,JVM模块(jvm.h/cpp)用于JNI接口的补充,所以用处和jni.h/cpp类似;
JNI - 加载动态库
假设代码如下:
public class Test{
public static void main(String[] args){
system.loadLibrary("HelloWorld"); //或 system.load("/../../../HelloWorld"); 绝对路径
}
}
Java阶段 - 加载分析
Java中加载库文件的方法有两种:
1、system.load(String libname)
/**
* Loads the native library specified by the filename argument. The filename
* argument must be an absolute path name.
* (for example
* <code>Runtime.getRuntime().load("/home/avh/lib/libX11.so");</code>).
*
* 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.
* A filename matching the argument does not have to exist in the file
* system. See the JNI Specification for more details.
*
* Otherwise, the filename argument is mapped to a native library image in
* an implementation-dependent manner.
* <p>
* First, if there is a security manager, its <code>checkLink</code>
* method is called with the <code>filename</code> as its argument.
* This may result in a security exception.
* <p>
* This is similar to the method {@link #loadLibrary(String)}, but it
* accepts a general file name as an argument rather than just a library
* name, allowing any file of native code to be loaded.
* <p>
* The method {@link System#load(String)} is the conventional and
* convenient means of invoking this method.
*
* @param filename the file to load.
* @exception SecurityException if a security manager exists and its
* <code>checkLink</code> method doesn't allow
* loading of the specified dynamic library
* @exception UnsatisfiedLinkError if either the filename is not an
* absolute path name, the native library is not statically
* linked with the VM, or the library cannot be mapped to
* a native library image by the host system.
* @exception NullPointerException if <code>filename</code> is
* <code>null</code>
* @see java.lang.Runtime#getRuntime()
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive
public void load(String filename) {
load0(Reflection.getCallerClass(), filename);
//注意传的参数是Reflection.getCallerClass(),getCallerClass本身就是一个native的方法
//解析:方法调用所在的方法必须用@CallerSensitive进行注解,通过此方法获取class时会跳过链路上所有的有@CallerSensitive注解的方法的类,直到遇到第一个未使用该注解的类,此处也就是最上面例子中Test类
}
synchronized void load0(Class<?> fromClass, String filename) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkLink(filename);
}
if (!(new File(filename).isAbsolute())) { //检查传入的filename是否为绝对路径
throw new UnsatisfiedLinkError(
"Expecting an absolute path of the library: " + filename);
}
ClassLoader.loadLibrary(fromClass, filename, true); //true表示为绝对路径
}
2、system.loadLibrary(String libname)
/**
* Loads the native library specified by the <code>libname</code>
* argument. The <code>libname</code> argument must not contain any platform
* specific prefix, file extension or path. If a native library
* called <code>libname</code> is statically linked with the VM, then the
* JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
* See the JNI Specification for more details.
*
* Otherwise, the libname argument is loaded from a system library
* location and mapped to a native library image in an implementation-
* dependent manner.
* <p>
* First, if there is a security manager, its <code>checkLink</code>
* method is called with the <code>libname</code> as its argument.
* This may result in a security exception.
* <p>
* The method {@link System#loadLibrary(String)} is the conventional
* and convenient means of invoking this method. If native
* methods are to be used in the implementation of a class, a standard
* strategy is to put the native code in a library file (call it
* <code>LibFile</code>) and then to put a static initializer:
* <blockquote><pre>
* static { System.loadLibrary("LibFile"); }
* </pre></blockquote>
* within the class declaration. When the class is loaded and
* initialized, the necessary native code implementation for the native
* methods will then be loaded as well.
* <p>
* If this method is called more than once with the same library
* name, the second and subsequent calls are ignored.
*
* @param libname the name of the library.
* @exception SecurityException if a security manager exists and its
* <code>checkLink</code> method doesn't allow
* loading of the specified dynamic library
* @exception UnsatisfiedLinkError if either the libname argument
* contains a file path, the native library is not statically
* linked with the VM, or the library cannot be mapped to a
* native library image by the host system.
* @exception NullPointerException if <code>libname</code> is
* <code>null</code>
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive //这个注释可以让使用getCallerClass时跳过本方法,再找上层调用的方法
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);
}
ClassLoader.loadLibrary(fromClass, libname, false); //false表示为非绝对路径
}
从两个函数的注释里就能看出来,load函数必须要libname的绝对路径,而loadLibrary则不做强制要求;
并且可以看到,load和loadLibrary最终都是由ClassLoader.loadLibrary()方法实现的加载;
3、ClassLoader.loadLibrary()
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class<?> fromClass, String name,
boolean isAbsolute) {
//明确参数的内容:fromclass向上追溯,一直到load内调用load0或loadLibrary调用loadLibrary0,然后传入的是调用load或loadLibrary函数的fromClass是Test类,也就是调用了system.loadLibrary的类
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader(); //loader是Test的类装载器,拿到类装载器有什么用???
//只有通过启动类加载器加载的类,比如String,其fromClass才会是null
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
//usr_paths和sys_paths的注释为: // The paths searched for libraries,即查找库文件的路径;
//如果还为空,则需要进行初始化处理;
}
if (isAbsolute) { //如果是绝对路径则进入该if条件内直接按照绝对路径进行加载,如果不是绝对路径,则需要按照lib默认地址处理
if (loadLibrary0(fromClass, new File(name))) {
//则通过ClassLoader.loadLibrary0()方法直接加载,可以怀疑,真正加载都由ClassLoader.loadLibrary0()完成。
return;
}
throw new UnsatisfiedLinkError("Can't load library: " + name); //比如找不到库或文件,则抛出exception
}
if (loader != null) { //如果获取到了Test类的类装载器,进入这个if条件
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
/*
protected String findLibrary(String libname) {
return null; //这个地方为什么恒定返回null呢?
}
看注释大概意思是如果该方法返回null,那么就按照默认的path寻找库文件,当虚拟机调用这个方法,来定位属于此类的加载器的库文件的位置;
可能是有自己实现的classloader重写了findlibrary,也重新定义了动态库的地址。
*/
File libfile = new File(libfilename);
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);
}
} //必然libfilename == null 所以继续向下
//遍历sys_paths下的子路径,查找是否存在目标文件,即libname
for (int i = 0 ; i < sys_paths.length ; i++) {
File libfile = new File(sys_paths[i], System.mapLibraryName(name)); //napLibraryName是native方法,将库名映射为对应的平台下的文件名称;
if (loadLibrary0(fromClass, libfile)) {
//尝试加载目标文件,加载成功了则返回,真正加载的还是loadLibrary0()方法
return;
}
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
if (loader != null) {
//如果sys_path路径下没加载到,并且该类也有类加载器,那么再从该类对应的lib库地址内遍历一遍,确定是否动态连接文件是否可加载到
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"); //如果sys_path和usr_path都没没找到文件,抛出异常:加载失败
}
3、ClassLoader.loadLibrary0()
从ClassLoader.loadLibrary()方法中得知:
ClassLoader.loadLibrary()最主要的功能就是在所有默认或重写findLibrary后指定的library路径下进行文件的遍历,然后真正进行加载的,是loadLibrary0方法;
private static boolean loadLibrary0(Class<?> fromClass, final File file) {
//fromClass还是调用调用system.load或loadLibrary的类;
//要加载的动态连接库文件,已经是一个File对象;
// Check to see if we're attempting to access a static library
String name = findBuiltinLib(file.getName());
//findBuiltinLib是native方法,作用是检查file是不是内置的动态链接库,还是程序员自己新写的,内置的则返回名字,非内置为null
boolean isBuiltin = (name != null);
if (!isBuiltin) { //如果非内置动态链接库文件,即新写的
boolean exists = AccessController.doPrivileged(
new PrivilegedAction<Object>() {
public Object run() {
return file.exists() ? Boolean.TRUE : null; //检查file是否存在,是返回true,不是返回null
}})
!= null;
if (!exists) {
return false;
}
try { //确定了file是存在的
name = file.getCanonicalPath(); //拿到file对应的规范路径名,也就是绝对路径
} catch (IOException e) {
return false;
}
}
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader(); //获取最初调用system.load或loadLibrary的类Test的类加载器
Vector<NativeLibrary> libs =
loader != null ? loader.nativeLibraries : systemNativeLibraries;
//fromClass!=null,则用nativeLibraries获取Test类已经加载过的共享库的缓存
//fromClass==null,则用systemNativeLibraries(等于null表示启动类加载器加载的类,比如String,那么就用系统的缓存库)
synchronized (libs) {
int size = libs.size();
for (int i = 0; i < size; i++) {
NativeLibrary lib = libs.elementAt(i);
if (name.equals(lib.name)) { //遍历lib(还存库,如果找到名字一样的,就是加载过了,返回true)
return true;
}
}
synchronized (loadedLibraryNames) {
//★★★★★★loadedLibraryNames是ClassLoader的静态属性,Vector<String>类型,表示全局的所有ClassLoader实例已加载的共享库的文件名的缓存★★★★★★
// All native library names we've loaded.
// private static Vector<String> loadedLibraryNames = new Vector<>();
if (loadedLibraryNames.contains(name)) {
//全局所有缓存库中有这个name,则表示加载过了,重复加载要抛个异常(为什么不忽略掉,而要抛个异常??)
throw new UnsatisfiedLinkError
("Native Library " +
name +
" already loaded in another classloader");
}
/* If the library is being loaded (must be by the same thread,
* because Runtime.load and Runtime.loadLibrary are
* synchronous). The reason is can occur is that the JNI_OnLoad
* function can cause another loadLibrary invocation.
*
* Thus we can use a static stack to hold the list of libraries
* we are loading.
*
* If there is a pending load operation for the library, we
* immediately return success; otherwise, we raise
* UnsatisfiedLinkError.
*/
int n = nativeLibraryContext.size();
// nativeLibraryContext是ClassLoader的静态属性,Stack<NativeLibrary>类型
// nativeLibraryContext 表示正在加载或者卸载的共享库缓存,所有等待被加载和被卸载的都在nativeLibraryContext数组中
// nativeLibraryContext 注释:native libraries being loaded/unloaded.
// private static Stack<NativeLibrary> nativeLibraryContext = new Stack<>();
for (int i = 0; i < n; i++) {
//遍历正在加载或卸载的库文件列表中有没有我们的目标库文件
NativeLibrary lib = nativeLibraryContext.elementAt(i);
if (name.equals(lib.name)) {
if (loader == lib.fromClass.getClassLoader()) {
return true; //如果有并且是同一个ClassLoader加载的,那就返回True,表示已经加载了
} else {
throw new UnsatisfiedLinkError
("Native Library " +
name +
" is being loaded in another classloader");
}
}
}
//如果是完全没有加载的,则要进行加载
NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
nativeLibraryContext.push(lib); // 所有等待被加载和被卸载的都在nativeLibraryContext数组中
try {
lib.load(name, isBuiltin); //ClassLoader.NativeLibrary.load()
// NativeLibrary是ClassLoader内部静态类,load是NativeLibrary中的native方法
// 也就是说,真正把库文件加载到JVM内存中的,还是Native方法
} finally {
nativeLibraryContext.pop(); //加载完了踢出去
}
if (lib.loaded) {
loadedLibraryNames.addElement(name); //加载成功,放到加载类的loadedLibraryNames数组中
libs.addElement(lib);
return true; //返回false,加载成功
}
return false;
}
}
}
从代码分析:
NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
如果完全没有加载过的库文件,则生成一个NativeLibrary对象,并且把对象放到nativeLibraryContext中,作为准备加载的内容,换句话说,这个内部类的作用就是表示一个已经加载过的库文件,加载后都保存在nativeLibraries属性中。
NativeLibrary类部分代码如下:
static class NativeLibrary {
// opaque handle to native library, used in native code.
long handle;
// the version of JNI environment the native library requires.
private int jniVersion; //jdk版本
// the class from which the library is loaded, also indicates
// the loader this native library belongs.
private final Class<?> fromClass; //哪个类加载的这个动态库文件
// the canonicalized name of the native library.
// or static library name
String name; //文件的绝对路径
// Indicates if the native library is linked into the VM
boolean isBuiltin; //是否为内置库文件
// Indicates if the native library is loaded
boolean loaded; //是否已经加载结束
......其余省略......
结论:最终真正将ddl、so文件加载到内存的,是JVM的load方法
假设一个情况:
如果多个类都加载了同一个动态库文件,比如:a.cpp,那么JVM如何处理?
JVM只会绑定第一个加载的库的实现方法,本地方法的这种特殊性是JVM调用操作系统加载动态链接库的实现决定的,同时C语言也不支持方法同名,为了避免同名,所以生成头文件的时候就会包含全限定类名。
JVM阶段 - 加载分析
class NativeLibrary中的native load,是一个由OpenJDK实现的方法:
OpenJDK/src/share/native/java/lang/ClassLoader.c/Java_java_lang_ClassLoader_00024NativeLibrary_load
/*
* Class: java_lang_ClassLoader_NativeLibrary
* Method: load
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_load /* 命名规范是正确的 */
(JNIEnv *env, jobject this, jstring name)
/* 第一个参数是JNIEnv */
{
const char *cname;
jint jniVersion;
jthrowable cause;
void * handle;
if (!initIDs(env))
return;
cname = JNU_GetStringPlatformChars(env, name, 0);
if (cname == 0)
return;
handle = JVM_LoadLibrary(cname); /* 也用到了虚拟机提供的 jvm.cpp中的方法 */
if (handle) {
const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
JNI_OnLoad_t JNI_OnLoad;
unsigned int i;
for (i = 0; i < sizeof(onLoadSymbols) / sizeof(char *); i++) {
JNI_OnLoad = (JNI_OnLoad_t)
JVM_FindLibraryEntry(handle, onLoadSymbols[i]);
if (JNI_OnLoad) {
break;
}
}
if (JNI_OnLoad) {
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
jniVersion = (*JNI_OnLoad)(jvm, NULL);
} else {
jniVersion = 0x00010001;
}
cause = (*env)->ExceptionOccurred(env);
if (cause) {
(*env)->ExceptionClear(env);
(*env)->Throw(env, cause);
JVM_UnloadLibrary(handle);
goto done;
}
if (!JVM_IsSupportedJNIVersion(jniVersion)) {
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);
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));
done:
JNU_ReleaseStringPlatformChars(env, name, cname);
}