JNI字符串,数组,字段和方法,对象引用,异常处理,多线程,内存回收等

  JNI中字符串,数组,字段和方法,局部引用和全局引用,异常处理,多线程等核心内容。

  Android NDK(九):JNI实践总结- https://blog.csdn.net/u013718120/article/details/65634526

-- finalize的作用:
 1.finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
 2.finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性

 3.不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象);② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。其原因可见下文[finalize的问题].

JNI与JVM的联系。

> JNI 对象引用;局部引用 全局引用 弱全局引用

  JNI局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)
-- JNI 中局部引用和全局引用- https://blog.csdn.net/tonyfield2015/article/details/8803380
 JNI给出实例和数组类型(如jobject,jclass,jstring,jarray)作为不透明的引用。Native代码从不会直接访问一个不透明的引用的内容。相反,它使用JNI函数访问由一个不透明的参考指向数据结构。只处理不透明的引用,你不用担心依赖于特定的Java虚拟机实现的内部对象布局。但是你的确需要进一步了解JNI中不同类型的引用:
 1.JNI 支持三种不透明引用:局部引用( local references),全局引用(global references)和弱全局引用( weak global references)。
 2.局部和全局引用有不同的生命周期,本地应用被自动释放,而全局应用和弱全局引用仅在显式调用释放命令时释放。
 3.一个局部和全局引用保持引用对象不被garbage内存回收。另一方面,弱全局引用是允许引用对象被garbage内存回收的。
 4.并非所有的引用都能在任何上下文中被使用。例如,在创建引用的native函数返回后使用这个引用是不合法的。

 有两种方法使一个局部引用无效。如前所述,当native方法返回后,虚拟机自动释放了方法执行期间创建的所有局部引用。另外,程序员可以用像DeleteLocalRef 这样的JNI函数显式管理局部引用的生命周期。既然在native方法返回后虚拟机会自动释放期间创建的所有局部引用,那为什么需要显式删除局部引用呢? 一个局部引用直到它无效时才会让garbage收集引用对象
 局部引用也仅在创建它们的线程中有效。在一个线程中创建的局部引用不能为另一个线程所用。在native 方法中存储一个局部引用到全局变量并期望在另一个线程中使用它是错误的。

   你能在一个native方法的多个调用间使用一个全局引用。一个全局引用也能在多线程件被使用,直到被程序员显式释放。类似局部引用,一个全局引用在被释放前保证引用对象不被garbage回收。
   和局部引用不同的是,没有那么多函数能够创建全局引用。能创建全局引用的函数只有 NewGlobalRef。
   弱全局引用是 Java 2 SDK r1.2中新增的。弱全局引用用 NewGlobalWeakRef 创建,用 DeleteGlobalWeakRef 释放。类似全局引用,弱全局引用在native方法和不同线程间保持有效。与之不同的是,弱全局引用不会阻止底层对象被garbage 收集。 
   Java 2 SDK r1.2 提供另一系列函数来管理局部引用的生命周期。这些函数是 EnsureLocalCapacity,New-LocalRef,PushLocalFrame,和 PopLocalFrame.如果需要创建更多的局部引用,一个native方法可能调用 EnsureLocalCapacity 来保证足够数量的局部引用有效。Push/PopLocalFrame 函数让程序员可以创建作用域嵌套的局部引用。PushLocalFrame 为特定数量的局部引用创建一个新的作用域。 PopLocalFrame 销毁最顶层作用域,释放这个作用域中的所有局部引用。使用 Push/PopLocalFrame 函数的优点是,他们是管理局部引用的生命周期成为可能,不必担心执行期间被创建的每个单个局部引用。当 native代码不再需要访问一个全局引用,你应当调用 DeleteGlobalRef 。
  如果没有成功调用这个函数,即使对象不再系统任何地方被使用的情况,Java虚拟机garbage也不会收集相应对象。    当 native代码不再需要访问一个弱全局引用,你应当调用 DeleteWeakGlobalRef。 如果没有成功调用这个函数,Java虚拟机garbage也会收集这个对象。但弱全局引用本身消耗的内存将无法回收。

-- JNI 局部引用、全局引用和弱全局引用- http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/recommend.html
一般有两种 native 代码:直接实现 native 方法的函数 和 在任何上下文中使用的工具函数。
    当编写直接实现 native 方法的函数,你需要小心循环中的过度创建局部引用,以及native 方法中不需要返回的不必要的局部引用。对于虚拟机,用上16个以内的局部引用待native方法返回后删除是可以接受的。Native 方法调用一定不能引起全局引用或弱全局引用的累积,因为全局引用和弱全局引用不会在native方法返回后自动释放。
  在编写native工具函数时你必须注意不要造成执行路径中任何的局部引用泄露。因为一个工具函数可能在无法预料的情况下被重复调用,任何没有必要创建的引用可能造成内存溢出。
 当调用一个返回原始类型的工具函数,它一定不能有累积各类型引用的副作用。
 当调用一个返回引用类型的工具函数,它一定不能累积各类型引用,除了作为结果返回的那个引用。
 JNI 引用使用不当造成引用表溢出,最终导致程序崩溃。

 启动一个 Java 程序,如果没有手动创建其它线程,默认会有两个线程在跑,一个是 main 线程,另一个就是 GC 线程(负责将一些不再使用的对象回收)。
  在 C++ 中 new 一个对象,使用完了还要做一次 delete 操作,malloc 一次同样也要调用 free 来释放相应的内存,否则你的程序就会有内存泄露了。
  在 C/C++ 中内存还分栈空间和堆空间,其中局部变量、函数形参变量、for 中定义的临时变量所分配的内存空间都是存放在栈空间(而且还要注意大小的限制),用 new 和 malloc 申请的内存都存放在堆空间。但 C/C++ 里的内存管理还远远不止这些,这些只是最基础的内存管理常识。

> JNI多线程

Android下的JNI创建多线程的方法- https://blog.csdn.net/panda1234lee/article/details/13503291

JNI DETECTED ERROR IN APPLICATION: can't call void XXX on instance of java.lang.Class <XXX>解决方案-https://blog.csdn.net/oMrLeft123/article/details/54601191

  问题1:
JNIEnv是一个线程相关的变量;JNIEnv 对于每个 thread 而言是唯一的 ;JNIEnv *env指针不可以为多个线程共用
解决办法:
但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针.可以使用javaAttachThread保证取得当前线程的Jni环境变量
static JavaVM *gs_jvm=NULL;
gs_jvm->AttachCurrentThread((void **)&env, NULL);//附加当前线程到一个Java虚拟机
jclass cls = env->GetObjectClass(gs_object);
jfieldID fieldPtr = env->GetFieldID(cls,"value","I");
  问题2:
不能直接保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它。
解决办法:
用env->NewGlobalRef创建一个全局变量,将传入的obj(局部变量)保存到全局变量中,其他线程可以使用这个全局变量来操纵这个java对象
注意:若不是一个 jobject,则不需要这么做。如:
jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。
而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。
static jobject gs_object=NULL;
JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj)
{
    env->GetJavaVM(&gs_jvm); //保存到全局变量中JVM 
    //直接赋值obj到全局变量是不行的,应该调用以下函数: 
    gs_object=env->NewGlobalRef(obj);

}

-- 子线程函数里需要使用AttachCurrentThread()和DetachCurrentThread()这两个函数。
1.在JNI_OnLoad中,保存JavaVM*,这是跨线程的,持久有效的,而JNIEnv*则是当前线程有效的。一旦启动线程,用AttachCurrentThread方法获得env。
2.通过JavaVM*和JNIEnv可以查找到jclass。
3.把jclass转成全局引用,使其跨线程。
4.然后就可以正常地调用你想调用的方法了。
5.用完后,别忘了delete掉创建的全局引用和调用DetachCurrentThread方法。

JNI的监视器允许原生代码利用Java对象同步,虚拟机保证存取监视器的线程能够安全执行,而其他线程等待监视器对象编程可用状态,例如:
synchronized(obj){
  /*同步线程安全代码块*/
}
在原生代码中相同级别同步可以用JNI的监视器方法实现的,例如:
if(JNI_OK == (*env)->MonitorEnter(env,obj)){
  /*错误处理*/
}
/*同步线程安全代码块*/
if(JNI_OK == (*env)->MonitorExit(env,obj)){
  /*错误处理*/
}
/*将当前线程附着到虚拟机上*/
(*cachedJvm)->AttachCurrentThread(cachedJvm,&env,NULL);
/*可以用JNIEnv接口实现线程与java应用程序的通信*/
/*将当前线程与虚拟机分离*/
(*cachedJvm)->DetachCurrentThread(cachedJvm);


> JNI内存回收
JNI引用与垃圾回收- http://blog.csdn.net/ordinaryjoe/article/details/7666571
evn->ReleaseStringUTFChars(signature, releaseMD5);
env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
  JNI是Android系统中底层和框架层通信的重要方式、JNI对于Android安全以及Android安全加固等都是有所帮助的。C语言中调用Java的一些函数,实际上也是反射获取的,步骤跟Java层的是一样的,换句话说在Java反射能做到的,在JNI中通过类似的反射也是可以做到的。
-- android jni 内存泄露- http://blog.csdn.net/liwei405499/article/details/42024773
JNI(java native interface)经常遇到到问题:
  问题1.  忘记delete local reference。带New到方法(如:NewByteArray)这样到方法比较好辨认,需要手动调用DeleteLocalRef()来释放(返回值除外)。比较特殊的一个方法是:GetByteArrayELement必须要调用ReleaseByteArrayElements进行释放。当然如果你只是取bytearray中到byte,那么完全可以用GetByteArrayRegion实现。
  问题2. 没有NewGlobalRef。 在不同线程调用java方法,需要保存jobject对象,这时需要对jobject对象做全局引用,否则会失效。
  问题3.  jbytearray的length。在JNI layer获取到jbytearray到长度是不对到,应该由java获取byte[]的length再传给C layer。否则C layer有可能获得到是乱码。
  问题4.  线程问题。 不同线程使用JNIEnv*对象,需要AttachCurrentThread将env挂到当前线程,否则无法使用env。
  问题5.  javap 命令是对java的class文件操作;而javah命令需要在包名到上一层路径运行才行,否则无法生成.h文件。
  问题6. 尽量避免频繁调用JNI或者是使用JNI传输大量到数据。
  问题7. Reference Table overflow (max=1024) 或者是 Reference Table overflow (max=512)一定是因为忘记释放global reference或者local reference,请仔细检查代码。
  问题8. 不要在windows下使用cygwin编译NDK code,那样会遇到arguments too long问题,因为windows路径长度有限制导致。虽然可以使用subst将路径映射为短路径,但是在编译时间和调试上,windows的孩子都是伤不起。同样到build,在windows下要15分钟左右,而在mac下只要5分多,相差3倍。调试JNI 代码到速度更是不用提了,差太多。

  总结,JNI代码量其实不是很多,JNI作为一个数据传输层,它的作用仅仅是java和c直接到桥梁,但是如果处理不好将会是灾难,调试和找bug非常困难。

>JNI异常

  在Android中我们处理异常的方式一般都是:发现异常、捕获异常(向上层抛出异常)、处理异常。JNI中对于异常的处理和Andrid很相似。基本的流程都是检查异常,捕获异常,抛出异常,处理异常。 JNI 编程的内存管理。

Android jni/ndk编程五:jni异常处理- https://blog.csdn.net/u011913612/article/details/52668422
-- Java、Dalvik VM、C/C++的运行机制与流程 在Android的NDK中,Java、C/C++、Dalvik VM关系如下:
 1、java的dex字节码和C/C++的*.so同时运行DalvikVM之内,共同使用一个进程空间。每次使用jni调用c/c++开辟一个线程去处理
 2、java和C/C++可以相互调用,调用的关键是DalvikVM
 3、一般而言,比较经典的模式是Java通过JNI的C组建和C++相互沟通,一般业务处理放在C/C++中
 4、C++代码处于核心控制地位更具价值

 当java需要C/C++代码时,在DalvikVM虚拟机中加载动态链接库时,会先调用JNI_Onload()函数,此时就会把javaVM对象的指针存储于C层JNI组建的全局环境中,在JAVA层调用C层的本地库函数时,调用C本地函数线程必然通过Dalvik VM来调用C本地函数,测试Dalvik虚拟机会为本地的C组建实例化一个JNIEnv指针,该指针指向Dalvik虚拟机的具体函数列表,当JNI的C组件调用java层方法和属性时,需要通过JNIEnv指针来进行调用。
 当C++组件主动调用Java层方法时,需要通过JNI的C组件把JNIEnv指针传递给C++组件,此后,c++组件即可通过JNIEnv指针来掌控Java层代码。

--- 对于JNI和NDK很多Android开发初学者没有搞明白这个问题: 
- JNI是Java调用Native机制,是Java语言自己的特性全称为Java Native Interface 
- 类似的还有微软.Net Framework上的p/invoke,可以让C#或Visual Basic.NET可以调用C/C++的API,所以说JNI和Android没有关系 
- 在PC上开发Java的应用,如果运行在Windows平台使用JNI是是经常的,比如说读写Windows的注册表。- 而NDK是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例代码 
- 我们可以理解为Windows Platform SDK一样,是纯C/C++编写的,但是Android并不支持纯C/C++编写的应用 
- 同时NDK提供的库和函数功能很有限,仅仅处理些算法效率敏感的问题

 -- 在java的编程中,我们经常会遇到各种的异常,也会处理各种的异常。处理异常在java中非常简单,我们通常会使用try-catch-finally来处理,也可以使用throw简单抛出一个异常。Java的异常和native 代码的异常或者错误处理是不同的机制。实践中发现,并不是JNI调用发生异常时,就会在调用函数处进行栈展开,而是在后续的某个JNI调用时,才发生异常退出,比如开头提到的attempt to use stale local reference错误,就是因为某个JNI调用抛出了异常,没有进行处理;结果在后续第N个调用时,才提示错误并推出,logcat也没有打印java的异常信息。这导致问题比较难定位。

 专用的JNI函数,可以对异常进行处理:
Throw():丢弃一个现有的异常对象;在固有方法中用于重新丢弃一个异常。
ThrowNew():生成一个新的异常对象,并将其丢弃。
ExceptionOccurred():判断一个异常是否已被丢弃,但尚未清除。
ExceptionDescribe():打印一个异常和堆栈跟踪信息。
ExceptionClear():清除一个待决的异常。
FatalError():造成一个严重错误,不返回。

    jint        (*Throw)(JNIEnv*, jthrowable);
    jint        (*ThrowNew)(JNIEnv *, jclass, const char *);
    jthrowable  (*ExceptionOccurred)(JNIEnv*);
    void        (*ExceptionDescribe)(JNIEnv*);
    void        (*ExceptionClear)(JNIEnv*);
    void        (*FatalError)(JNIEnv*, const char*);


1> ExceptionCheck:检查是否发生了异常,若有异常返回JNI_TRUE,否则返回JNI_FALSE 
2> ExceptionOccurred:检查是否发生了异常,若用异常返回该异常的引用,否则返回NULL 
3> ExceptionDescribe:打印异常的堆栈信息 
4> ExceptionClear:清除异常堆栈信息 
5> ThrowNew:在当前线程触发一个异常,并自定义输出异常信息 
6> Throw:丢弃一个现有的异常对象,在当前线程触发一个新的异常 
7> FatalError:致命异常,用于输出一个异常信息,并终止当前VM实例(即退出程序)

 在所有这些函数中,最不能忽视的就是ExceptionOccurred()和ExceptionClear()。大多数JNI函数都能产生异常,而且没有象在Java的try块内的那种语言特性可供利用。所以在每一次JNI函数调用之后,都必须调用ExceptionOccurred(),了解异常是否已被丢弃。若侦测到一个异常,可选择对其加以控制(可能时还要重新丢弃它)。然而,必须确保异常最终被清除。这可以在自己的函数中用ExceptionClear()来实现;若异常被重新丢弃,也可能在其他某些函数中进行。

 举个最简单的例子,如果调用Java中的方法后出现异常,忽略。
jobject objectAttr = (*env)->CallObjectMethod(env, objectDocument, createAttributeMid, stoJstring(env, "ABC"));
// deal with exception
exc = (*env)->ExceptionOccurred(env);
if(exc) {
      (*env)->ExceptionClear(env);
      doSomething();

> JNI 资源释放
 关于JNI 资源释放- https://blog.csdn.net/lxb00321/article/details/62881171
一、多次NewByteArray后,报错“ReferenceTable overflow”
解决办法:释放所有对object的引用
例: jbyteArray audioArray = jnienv->NewByteArray(frameSize);
       jnienv->SetByteArrayRegion(audioArray,0,frameSize,(jbyte*)fReceiveBuffer);
       jnienv->DeleteLocalRef(audioArray);

1.FindClass 
例如,jclass ref= (env)->FindClass("java/lang/String");
env->DeleteLocalRef(ref); 
 
2.NewString/ NewStringUTF/NewObject/NewByteArray
例如,jstring     (*NewString)(JNIEnv*, const jchar*, jsize);    
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);     
void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring     (*NewStringUTF)(JNIEnv*, const char*);    
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);     

void        (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
env->DeleteLocalRef(ref);
 
3.GetObjectField/GetObjectClass/GetObjectArrayElement
jclass ref = env->GetObjectClass(robj);
env->DeleteLocalRef(ref); 
 
4.GetByteArrayElements
jbyte* array= (*env)->GetByteArrayElements(env,jarray,&isCopy);
(*env)->ReleaseByteArrayElements(env,jarray,array,0);
 
5.const char* input =(*env)->GetStringUTFChars(env,jinput, &isCopy);
(*env)->ReleaseStringUTFChars(env,jinput,input);
 
6.NewGlobalRef/DeleteGlobalRef
 jobject     (*NewGlobalRef)(JNIEnv*, jobject);     
 void        (*DeleteGlobalRef)(JNIEnv*, jobject);
例如,jobject ref= env->NewGlobalRef(customObj);
env->DeleteGlobalRef(customObj);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值