JNI 学习笔记-- JNI访问数组引用异常处理缓存策略
Posted 李樟清
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JNI 学习笔记-- JNI访问数组引用异常处理缓存策略相关的知识,希望对你有一定的参考价值。
1. 访问数组
1. 基本类型的数组
package com.zeking.jni;
public class JniTest03
public native void getArray(int[] intArray);
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
int [] intArray = 99,66,77,33,22,88;
jniTest03.getArray(intArray);
#include "com_zeking_jni_JniTest03.h"
#include <string.h>
#include <Windows.h>
/*
* Class: com_zeking_jni_JniTest03
* Method: getArray
* Signature: ([I)V
*/
// 访问基本类型数据数组
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_getArray
(JNIEnv * env, jobject jobj, jintArray jintArray)
int compare(jint *a,jint *b);
jint *elements = (*env)->GetIntArrayElements(env, jintArray, NULL);
if (elements == NULL)
return;
int length = (*env)->GetArrayLength(env, jintArray);
int i = 0;
for (i;i < length; i++)
printf("排序前:第%d个值是:%d\\n",i,elements[i]);
qsort(elements,length,sizeof(jint),compare);
int j = 0;
for (j; j < length; j++)
printf("排序后:第%d个值是:%d\\n", j, elements[j]);
(*env)->ReleaseIntArrayElements(env, jintArray, elements, JNI_COMMIT);
int compare(jint *a ,jint *b)
return *a - *b;
2. 引用类型的数组
package com.zeking.jni;
public class JniTest03
public native String[] initStringArray(int size);
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
String[] arr = jniTest03.initStringArray(5);
for(int i = 0; i< arr.length;i++)
System.out.println(arr[i]);
/*
* Class: com_zeking_jni_JniTest03
* Method: initStringArray
* Signature: (I)[Ljava/lang/String;
*/
// 访问引用数据类型的数组
JNIEXPORT jobjectArray JNICALL Java_com_zeking_jni_JniTest03_initStringArray
(JNIEnv *env, jobject jobj, jint size)
int i;
jclass jclz = (*env)->FindClass(env, "java/lang/String");
if (jclz == NULL)
return NULL;
// 创建jobjectArray
jobjectArray result = (*env)->NewObjectArray(env, size, jclz, jobj);
if (result == NULL)
return NULL;
// 赋值
for (i = 0; i < size; i++)
// C 字符串
char * c_str = (char*)malloc(256);
memset(c_str, 0, 256);
// 将int 转换成为char
sprintf(c_str, "hello num:%d\\n", i);
// C ->jstring
jstring str = (*env)->NewStringUTF(env, c_str);
if (str == NULL)
return NULL;
// 将jstring 赋值给数组
(*env)->SetObjectArrayElement(env, result, i, str);
free(c_str);
c_str = NULL;
// 返回jobjectArray
return result;
2. JNI引用
1. 局部引用
package com.zeking.jni;
public class JniTest03
public native void localRef();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
jniTest03.localRef();
/*
* Class: com_zeking_jni_JniTest03
* Method: localRef
* Signature: ()V
*/
// JNI 引用
// 局部引用
// 用来在JNI层生成JVM里面非基本类型数据结构的方法都是可以用于定义局部引用
// 定义方式多样:FindClass,NewObject,GetObjectClass,NewCharArray.... NewLocalRef()
// 释放方式: 1 方法调用完JVM 会自动释放 2.DeleteLocalRef
// 为什么 会自动释放,我们还要手动释放呢?在JNI里面会有一个 JNI局部引用表,
// 在android里面,整个android系统给 这个表 分配的内存他能够存储 比如 512个,
// 也就是说在android系统只能在同一时刻最多创建512个局部引用,每创建一个引用
// JVM就会把他放到这个JNI局部引用表里面来,如果没有手动及时释放,很可能创建的
// 引用一下子就达到了512个,音视频里面容易导致溢出
// 局部引用 : 不能在多线程里面使用,只能应用于定义的接口里面
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_localRef
(JNIEnv * env, jobject jobj)
int i = 0;
for (i = 0; i < 5; i++)
jclass jclz = (*env)->FindClass(env, "java/util/Date");
// 这个并不是引用,因为他没有定义一个对象,创建一个JVM里面的数据类型对象
jmethodID jmid = (*env)->GetMethodID(env, jclz, "<init>", "()V");
// 创建一个Date类型的局部引用
jobject jobj = (*env)->NewObject(env, jclz, jmid);
// 使用这个引用 ,下面代码省略
// 释放这个引用
(*env)->DeleteLocalRef(env, jclz);
(*env)->DeleteLocalRef(env, jobj);
2. 全局引用
package com.zeking.jni;
public class JniTest03
public native void createGlobalRef();
public native String getGlobalRef();
public native void delGlobalRef();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
jniTest03.createGlobalRef();
System.out.println(jniTest03.getGlobalRef());
jniTest03.delGlobalRef();
System.out.println("globalRef is released");
// 全局引用
// 跨进程,跨方法使用(可以多线程使用)
// NewGlobalRef 是创建全局引用的唯一方法
jstring global_str;
/*
* Class: com_zeking_jni_JniTest03
* Method: createGlobalRef
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_createGlobalRef
(JNIEnv * env, jobject jobj)
jobject obj =(*env)->NewStringUTF(env, "JNI is intersting");
global_str = (*env)->NewGlobalRef(env, obj);
/*
* Class: com_zeking_jni_JniTest03
* Method: getGlobalRef
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_getGlobalRef
(JNIEnv * env, jobject jobj)
return global_str;
/*
* Class: com_zeking_jni_JniTest03
* Method: delGlobalRef
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_delGlobalRef
(JNIEnv *env, jobject jobj)
(*env)->DeleteGlobalRef(env, global_str);
//弱全局引用
//它不会阻止GC,/跨线程,跨方法使用
//NewWeakGlobalRef 是创建弱全局引用的唯一方法
jclass g_weak_cls;
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_createWeakRef
(JNIEnv * env, jobject jobj)
jclass cls_string = (*env)->FindClass(env, "java/lang/String");
g_weak_cls = (*env)->NewWeakGlobalRef(env, c ls_string);
return g_weak_cls;
3. 异常
package com.zeking.jni;
public class JniTest03
public native void exception();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
try
// 如果不做处理,java是补获不到 exception,后面的java代码没办法继续执行
jniMain.exception();
catch (Exception e)
System.out.println(e.toString());
System.out.println("----------------异常发生后-------------");
/*
* Class: com_zeking_jni_JniTest03
* Method: exception
* Signature: ()V
*/
// 异常展示
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_exception
(JNIEnv * env, jobject jobj)
jclass jlaz = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, jlaz, "key","Ljava/string/String");
printf("exception \\n");
下面是控制台输出的内容
当我们运行的时候会报错,
当我们在找 key 这个id 的时候 ,不存在
但是 printf(“exception \\n”); 还是打印出来了,如果是java的话,如果报了异常,我们不去捕获他,后面的代码没办法继续执行
Exception in thread "main" java.lang.NoSuchFieldError: key
at com.zeking.jni.JniTest03.exception(Native Method)
at com.zeking.jni.JniTest03.main(JniTest03.java:40)
exception
如何让java层顺利的执行后面的代码呢?
如何让java层顺利的捕获到jni的异常呢?
/*
* Class: com_zeking_jni_JniTest03
* Method: exception
* Signature: ()V
*/
// 抛出异常
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_exception
(JNIEnv * env, jobject jobj)
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
//检查是否发送异常,java 里面的检查是 try catch
jthrowable ex = (*env)->ExceptionOccurred(env);
// 判断异常是否发送 ,相当于 catch ,说明发生了异常
if (ex != NULL)
jclass newExc;
//清空JNI 产生的异常,如果只是这句代码,没有后面的,这样清空了代码,java层可以继续执行后面的代码
//即可以打印出----------------异常发生后------------- 这个字符串
(*env)->ExceptionClear(env);
// 模拟 java中的 IllegalArgumentException
newExc = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if (newExc == NULL)
printf("exception newExc NULL \\n");
return;
(*env)->ThrowNew(env, newExc, "Throw exception from JNI: GetFieldID faild ");
结果打印
java.lang.IllegalArgumentException: The exception from JNI:GetFieldID failed
-----------发生异常后 ----------
exception
后面会写一篇关于 异常的工具类的 文章,还有 打印log的工具类的文章
4. 缓存
1. 局部静态变量进行缓存
package com.zeking.jni;
public class JniTest03
public String key = "zeking";
public native void cached();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
for (int i = 0; i < 5; i++)
jniTest03.cached();
/*
* Class: com_zeking_jni_JniTest03
* Method: cached
* Signature: ()V
*/
// 局部静态变量进行缓存.
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_cached
(JNIEnv * env, jobject jobj)
jclass jclz = (*env)->GetObjectClass(env, jobj);
// static 关键字就是告诉我们他是一个静态变量,如果定义的静态变量是一个局部变量
// 这个局部变量会存储在 static的 静态存储区,
// 意味着他只要定义初始化一次,以后在访问同一个静态区的时候,
// 他会一直有定义,不会被释放
static jfieldID fid = NULL;
if (fid == NULL)
fid = (*env)->GetFieldID(env, jclz,"key", "Ljava/lang/String;");
printf("GetFieldId \\n");
2. 全局变量进行缓存
package com.zeking.jni;
public class JniTest03
public String key = "zeking";
public native static void cachedGlobal();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
for (int i = 0; i < 5; i++)
cachedGlobal();
static jfieldID global_fid;// static 全局只能在这个定义的后面才能被使用,
/*
* Class: com_zeking_jni_JniTest03
* Method: cachedGlobal
* Signature: ()V
*/
// 全局变量
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_cachedGlobal
(JNIEnv * env, jclass jclz)
if (global_fid == NULL)
global_fid = (*env)->GetFieldID(env, jclz, "key", "Ljava/lang/String;");
printf("GetFieldId Global\\n");
5. 缓存策略和弱引用联合使用带来的问题
package com.zeking.jni;
public class Refence
public int getRef(int num)
return num;
package com.zeking.jni;
public class JniTest03
public native String acessCacheNewString();
public native String acessCF();
static
System.loadLibrary("JniTest03");
public static void main(String[] args)
JniTest03 jniTest03 = new JniTest03();
for(int i = 0 ; i < 10 ; i++)
jniTest03.acessCF();
long[] arr = new long[1024*1024];
/*
* Class: com_zeking_jni_JniTest03
* Method: acessCacheNewString
* Signature: ()Ljava/lang/String;
*/
// 缓存策略和弱引用联合使用带来的问题
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_acessCacheNewString
(JNIEnv * env , jobject jobj)
// 定义一个静态的局部变量
static jclass cls_string = NULL;
if (cls_string == NULL)
printf("in Java_com_zeking_jni_JniTest03_acessCacheNewString \\n");
// 给局部静态变量赋一个局部引用
cls_string = (*env)->FindClass(env,"com/zeking/jni/Refence" );
// 使用这个静态局部变量 ,, 第二次进来,这边可能出现了问题,因为这个cls_string 可能指向一个被JVM释放,指向一个野指针。
jmethodID jmi = (*env)->GetMethodID(env, cls_string, "getRef", "(I)I");
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex != NULL)
jclass newExc;
// 让java继续运行
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
printf("c exception happend\\n");
printf("out Java_com_zeking_jni_JniTest03_acessCacheNewString \\n");
return NULL;
/*
* Class: com_zeking_jni_JniTest03
* Method: acessCF
* Signature: ()Ljava/lang/String;
*/
// 调用 acessCF 方法 其实他里面就是调用了acessCacheNewString方法,
// 当调用完 acessCF 方法之后,他里面的东西就被释放了,但是
// acessCacheNewString 中的cls_string 是一个 静态的 局部变量,他没有被释放
// 当我们再次调用 AcessCF 方法之后,cls_string 指向的是一个被释放的区域(指向一个野指针)
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_acessCF
(JNIEnv * env, jobject jobj)
return Java_com_zeking_jni_JniTest03_acessCacheNewString(env, jobj);
下面就是控制台输出的内容 ,发生了异常
java.lang.NoSuchMethodError: getRef
at com.zeking.jni.JniTest03.acessCF(Native Method)
at com.zeking.jni.JniTest03.main(JniTest03.java:67)
java.lang.NoSuchMethodError: getRef
at com.zeking.jni.JniTest03.acessCF(Native Method)
at com.zeking.jni.JniTest03.main(JniTest03.java:67)
in Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
out Java_com_zeking_jni_JniTest03_acessCacheNewString
c exception happend
out Java_com_zeking_jni_JniTest03_acessCacheNewString
c exception happend
out Java_com_zeking_jni_JniTest03_acessCacheNewString
Exception in thread "main" Exception in thread "main"
以上是关于JNI 学习笔记-- JNI访问数组引用异常处理缓存策略的主要内容,如果未能解决你的问题,请参考以下文章
JNI/NDK开发指南——访问数组(基本类型数组与对象数组)
IntelliJ IDEA平台下JNI编程—本地C代码创建Java对象及引用