Java - JNI

在看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++ 基础数据类型对应表如下:

JavaJVM<jni.h>定义的中间类型C/C++类型备注
bytejbytesigned char定义于jni_x86.h中
intjint/jsizeint定义于jni_x86.h中,在<jni.h>中又将jint起名为:jsize
longjlonglong定义于jni_x86.h中
charjcharunsigned short定义于jni.h中
booleadjbooleanunsigned char定义于jni.h中
shortjshortshort定义于jni.h中
floatjfloatfolat定义于jni.h中
doublejdoubledouble定义于jni.h中

除了基础数据类型,肯定还有对象类型,从代码中的定义来看,所有的对象类型都继承于_jobject,Java - JNI - C/C++ 对象类型对应表如下:
JNI对象类型继承关系图

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值