本文来自于腾讯Bugly公众号(weixinBugly), 作者:qingcuilu,未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/AtxEEN13z_sdbZTpJsKWLw
在Android编程中,出于硬件交互,跨平台,安全性,第三方库等方面的考虑,我们需要Java与C/C++互相调用,这就需要借助Java平台的JNI接口(Java Native Interface)。Android早期版本因JNI调用性能,native代码调试困难而被诟病,但近年来性能已经有不错的优化,Android NDK对C++开发支持也越来越好,特别是在Android Studio上开发调试C++代码极为方便。
然而JNI使用上还是有不少的坑和需要注意之处,特别是在多线程场景下使用JNI,不注意的话很容易出Bug。笔者结合自身经验、网上资料对JNI的坑进行总结,如果有不正确或遗漏之处欢迎指出。
局部引用超限
当我们通过FindClass,NewStringUtf等获取jclass或jobject,如果没有调用DeleteLocalRef删除局部引用,可能会出现内存泄漏或局部引用超限(local reference table overflow)的问题。
局部引用(Local Reference)是native code中对Java对象的映射,相当于持有一个Java对象的引用。局部引用属于JNI的引用类型,即是jobject或其子类。局部引用限于其创建的堆栈帧和线程,并且在其创建的堆栈帧返回时会自动删除。也就是说一般情况下局部引用会在返回Java方法时自己删除。但调用过程中如果存在循环、递归等调用层次过多的情况,很可能会导致局部引用数量超过局部引用限制导致崩溃。另一方面如果本地方法没有返回Java层,或本地线程没有断开与JVM的连接,局部引用无法自动释放会导致内存泄漏或局部引用超限的问题。
因此,我们定制规范,在局部引用使用完毕后,需要尽快调用DeleteLocalRef
手动删除局部引用。
未调用DetachCurrentThread导致线程无法正常退出
在natvie线程中调用了AttachCurrentThread
连接到虚拟机,但线程退出前未调用DetachCurrentThread
取消连接,会导致线程无法正常退出,有类似错误日志:”thread exiting, not yet detached”,甚至导致VM abort。
JNIEnv是一个指向全部JNI方法的指针。该指针只在创建它的线程有效,不能跨线程传递。如果是从Java层通过native方法调用到C/C++方法,则会创建一个栈桢(stack frame)储存虚拟机相关信息,包括JNIEnv指针,即在native函数的入参处可获得。且此种情况不需要调用DetachCurrentThread
取消连接。如果是在native层通过pthread_create等方式创建的线程,则需要调用了AttachCurrentThread
连接到虚拟机,才能获取JNIEnv指针。且在线程退出前需要调用DetachCurrentThread
取消连接。
因此,对于native线程,在调用JNI方法前可以先Attach,调用完成后立即Detach。不过这样手动调用显得较为繁琐。Google官方JNI指南文档建议在Android2.0以上可使用pthread_key,在线程析构时自动调用Detach以简化操作。
Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific to store the JNIEnv in thread-local-storage; t