胡琪

为今天工作,为明天投资,为未来孵化一些东西!

JNI开发注意事项集锦

内存释放

这个是JNI开发必须得注意的,因为JNI开发不像java开发,在java中我们基本上不用去考虑内存释放的问题,因为java的垃圾回收机制会处理这类问题,虽然说JNI层的Local Reference的生命周期持续到一个Native Method的结束,当Native Method返回时Java Heap中的对象不再被持有,也将会被GC回收,但是在JNI中如果我们使用了过多的局部引用,但是未及时释放这些内存,可能会导致出现局部引用表溢出的错误local reference table overflow (max=512),这个错误和安卓中的OOM类似,都是内存不够导致程序crash。之所以会出现局部引用表溢出的错误,是因为当代码从java层进入JNI层的时候虚拟机会创建一个local reference table来存储局部引用,但是这张表的大小是有限制的,一般为512项,当在JNI层创建一个局部引用时就会将其添加到该表中,如果创建的局部引用的个数超过了local reference table的大小就会出现local reference table overflow (max=512)这个错误,因此当我们创建了一个局部引用时在其不使用的地方最好释放掉,尤其是当我们在for/while循环中创建了局部引用的时候,一定要注意局部引用的释放问题。一般下面这些API获取的对象需要调用对应的API释放内存。因为这些API的调用会创建一个局部引用(不是说创建了局部引用就一定要释放掉,而是说这是一种好的习惯,能够避免出现局部引用表溢出的错误)

对象创建:

  1. jclass FindClass(JNIEnv *env, const char *name)
  2. jclass GetObjectClass(JNIEnv *env, jobject obj)
  3. jobject GetObjectField(JNIEnv *env, jobject obj,jfieldID fieldID)
  4. jobject CallObjectMethod(JNIEnv *env,jobject obj,jmethodID methodID)

《JNI开发注意事项集锦》从上述这些API的返回值类型可以看到这些API都会返回一个对象,对于此类API创建的对象统一调用void DeleteLocalRef(JNIEnv *env, jobject localRef)来释放对象

字符串的创建与释放

  1. const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy)对应—>void ReleaseStringUTFChars(JNIEnv *env, jstring string,const char *utf);
  2. jstring NewStringUTF(JNIEnv *env, const char *bytes)

NewStringUTF调用void DeleteLocalRef(JNIEnv *env, jobject localRef)来释放,因为字符串本身是一种特殊的对象类型

数组系列对象创建与释放

NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,ArrayType array, jboolean *isCopy)对应—>void Release<PrimitiveType>ArrayElements(JNIEnv *env,ArrayType array, NativeType *elems, jint mode)

调用对应的数组类型释放函数,如GetByteArrayElements则调用ReleaseByteArrayElements,GetIntArrayElements则调用ReleaseIntArrayElements

注意:JNI中的局部引用和C/C++中的局部变量是完全不同的概念,这两者之间存在本质区别。https://www.ibm.com/developerworks/cn/java/j-lo-jnileak/index.html

调用GetObjectField函数获取对象后在使用前最好进行下判空操作

在JNI中使用GetObjectField函数获取对象很容易出现获取对象失败的情况,因此在使用该函数得到对象后在使用前最好进行下判空操作,像下面这样:

多线程场景下FindClass非系统类失败

在自己创建的线程(类似通过pthread_create)中调用FindClass查找非系统类会失败,返回值为NULL,这是因为通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类,如自己在java层定义的类会返回NULL。《JNI开发注意事项集锦》

因此解决这个问题有2种思路:

  1. 在从java代码进入到JNI代码的主线程中先创建一个全局的类对象,这样在任何一个子线程中都可以直接使用
  2. 缓存应用类的ClassLoader,这样在子线程中通过缓存的ClassLoader来查找类,而不是通过JNI env来FindClass

第一种思路适合在子线程中查找自定义类较少的情况,这样可以直接在主线程中创建一个全局的引用对象,然后直接在其他线程中使用,从而避免在子线程中调用FindClass,示例代码如下:

 

如果在子线程中要使用自定义的Common这个类,通过env->FindClass会查找失败,首先定义一个全局对象g_common,然后在主线程中,如在JNI_OnLoad的逻辑中通过env->FindClass查找到该类,然后通过NewGlobalRef创建一个全局对象g_common,这个全局对象g_common就可以被其他线程直接使用。如果其他线程中需要使用的自定义类较多,则需要创建的全局对象较多,此时不推荐使用这种思路,推荐使用第二种思路。关于第二种思路请参考:https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653579453&idx=1&sn=6fe3cd3002edbfedf56f8fc30bd158c0&chksm=84b3babab3c433ac4b9d5165ff2c1611da76da800b5b4bfbce45f34650c78ff279b551fbbe13&mpshare=1&scene=1&srcid=1130B8HIZIFElNEUfTg1PPnl#rd

 

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注