JNI 引用、异常处理和缓存策略

一、JNI 引用变量

1、引用类型

JNI 引用的类型分为局部引用和全局引用

2、引用的作用

在JNI中告知虚拟机何时回收一个 JNI 变量

3、局部引用的使用

通过DeleteLocalRef 手动释放

  1. 访问一个很大的java对象,使用完成之后,还要进行复杂的耗时操作
  2. 创建了大量的局部引用,占用了太多的内存,而且这些局部引用跟后面的操作没有关联性。

例如:

  • 编写JNITest.java文件
public class JNITest {
    
    public native void localRef();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.localRef();
    }

	
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    localRef
 * Signature: 
 */
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_localRef
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"
#include <stdlib.h>

int compare(int *a,int *b){
    return (*a) - (*b);
}

//模拟:循环创建数组 
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_localRef
(JNIEnv *env, jobject jobj) {
	int i = 0;
	for(; i < 5; i++){
	    //创建Date对象
	    jclass cls = (*env)->FindClass(env,"java.util.Date");
	    jmethodID constructor_mid = (*env)->GetMethodID(env,cls,"<init>","()V");
	    jobject obj = (*env)->NewObject(env,cls,constructor_mid);
	    //此处省略100行代码
	    //不再使用obj对象了
	    //通知垃圾回收器回收这些对象
	    (*env)->DeleteLocalRef(env,obj);
	    //此处省略100行代码
	}
}

4、全局引用的使用

全局引用可以共享(跨多个方法,多个线程),手动控制内存使用(不再使用时通过 DeleteGlobalRef 手动释放)

例如:

  • 编写JNITest.java文件
public class JNITest {
    
    public native void createGlobalRef();
    
     public native String getGlobalRef();
     
     public native void deleteGlobalRef();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.createGlobalRef();
        t.getGlobalRef();
        t.deleteGlobalRef();
    }

	
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    createGlobalRef
 * Signature: 
 */
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_createGlobalRef
(JNIEnv *, jobject);

/*
 * Class:     com_example_jni_JNITest
 * Method:    getGlobalRef
 * Signature: 
 */
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getGlobalRef
(JNIEnv *, jobject);

/*
 * Class:     com_example_jni_JNITest
 * Method:    deleteGlobalRef
 * Signature: 
 */
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_deleteGlobalRef
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"
#include <stdlib.h>

//全局引用
jstring global_str;

JNIEXPORT void JNICALL Java_com_example_jni_JNITest_createGlobalRef
(JNIEnv *env, jobject jobj) {
    jstring obj = (*env)->NewStringUTF(env,"JNI development is powerful!");
    global_str = (*env)->NewGlobalRef(env,obj);
}



JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getGlobalRef
(JNIEnv *env, jobject jobj) {
    return global_str;
}

JNIEXPORT void JNICALL Java_com_example_jni_JNITest_deleteGlobalRef
(JNIEnv *env, jobject jobj) {
    (*env)->DeleteGlobalRef(env,global_str);
}

5、弱全局引用的使用

弱全局引用可以节省内存,在内存不足时可以释放所引用的对象,可以引用一个不常用的对象,如果为NULL,再临时创建

创建:NewWeakGlobalRef

销毁:DeleteGlobalWeakRef

二、JNI 的异常处理

JNI自己抛出的异常,在java层无法被捕获,只能在C层清空;用户通过ThrowNew抛出的异常,可以在Java层捕获

  1. 保证java代码可以继续运行
  2. 补救措施,保证 C 代码继续执行

例如:

  • 编写JNITest.java文件
public class JNITest {
    private String key="World!";
    
    public native void exception();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.exception();
    }

	
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    exception
 * Signature: 
 */
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_exception
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"
#include <stdlib.h>


JNIEXPORT void JNICALL Java_com_example_jni_JNITest_exception
(JNIEnv *env, jobject jobj) {
   jclass cls = (*env)->GetObjectClass(env,jobj);
   jfieldID fid = (*env)->GetFieldID(env,cls,"key2","Ljava/lang/String;");
   //检测是否发生java异常
   jthrowable exception = (*env)->ExceptionOccurred(env);
   if(exception != NULL){
       //让java代码可以继续运行
       //清空异常信息
       (*env)->ExceptionClear(env);
       fid = (*env)->GetFieldID(env,cls,"key","Ljava/lang/String;");
   }
   jstring jstr = (*env)->GetObjectField(env,jobj,fid);
   char *str = (*env)->GetStringUTFChars(env,jstr,NULL);
   //比对属性值是否合法,i 忽略大小写
   if(_stricmp(str,"Hello World!")!=0){
       //人为抛出异常,交给Java层处理
       jclass newExCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");
       (*env)->ThrowNew(env,newExCls,"Key's value is invalid!");
   }
   
}

三、JNI 缓存策略

1、局部的静态变量

局部的静态变量,当程序运行结束之后,变量的值还会在内存中

例如:

  • 编写JNITest.java文件
public class JNITest {
    
    private String key="Hello World!";
    public native String cached();

    public static void main(String[] args){
        JNITest t = new JNITest();
        for (int 1 = 0; i<100; i++){
           System.out.pringln("第"+(i+1)+"次执行,结果为:"+t.cached()); 
        }
    }

	
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    cached
 * Signature: 
 */
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_cached
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"
#include <stdlib.h>


JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_cached
(JNIEnv *env, jobject jobj) {
   jclass cls = (*env)->GetObjectClass(env,jobj);
   static jfieldID key_id = NULL;
   //获取jfieldID只获取一次
   if(key_id == NULL){
       key_id = (*env)->GetFieldID(env,cls,"key","Ljava/lang/String;");
       printf("------------GetFieldID--------\n")
   }
}

2、全局变量

全局变量在动态库加载完成之后,立刻缓存起来

//初始化全局变量
jfieldID key_fid;
jmethodID random_mid;
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_initIds
(JNIEnv *env, jclass jcls) {
    key_fid = (*env)->GetFieldID(env,jcls,"key","Ljava/lang/String;");
    random_mid = (*env)->GetMethodID(env,jcls,"getRandomInt","(I)I");
}