未正确使用全局引用
本机可以创建一些全局引用,以保证对象在不再需要时才会被垃圾收集器回收。常见的缺陷包括忘记删除已创建的全局引用,或者完全失去对它们的跟踪。考虑一个本机创建了全局引用,但是未删除它或将它存储在某处:
lostGlobalRef(JNIEnv* env, jobject obj, jobject keepObj) { jobject gref = (*env)->NewGlobalRef(env, keepObj); } |
创建全局引用时,JVM 会将它添加到一个禁止垃圾收集的对象列表中。当本机返回时,它不仅会释放全局引用,应用程序还无法获取引用以便稍后释放它 — 因此,对象将会始终存在。不释放全局引用会造成各种问题,不仅因为它们会保持对象本身为活动状态,还因为它们会将通过该对象能接触到的所有对象都保持为活动状态。在某些情况下,这会显著加剧内存泄漏。
避免常见缺陷
假设您编写了一些新 JNI 代码,或者继承了别处的某些 JVI 代码,如何才能确保避免了常见缺陷,或者在继承代码中发现它们?表 1 提供了一些确定这些常见缺陷的技巧:
表 1. 确定 JNI 编程缺陷的清单
未缓存触发数组副本错误界限过多回访使用大量本地引用使用错误的 JNIEnv未检测异常未检测返回值未正确使用数组未正确使用全局引用
规范验证 X X X
方法跟踪X X X X X X X
转储 X
-verbose:jni X
代码审查X X X X X X X X X X
您可以在开发周期的早期确定许多常见缺陷,方法如下:
根据规范验证新代码
分析方法跟踪
使用 -verbose:jni 选项
生成转储
执行代码审查
根据 JNI 规范验证新代码
维持规范的限制列表并审查本机与列表的遵从性是一个很好的实践,这可以通过手动或自动代码分析来完成。确保遵从性的工作可能会比调试由于违背限制而出现的细小和间断性故障轻松很多。下面提供了一个专门针对新开发代码(或对您来说是新的)的规范顺从性检查列表:
验证 JNIEnv 仅与与之相关的线程使用。
确认未在 GetXXXCritical() 的 ReleaseXXXCritical() 部分调用 JNI 方法。
对于进入关键部分的方法,验证该方法未在释放前返回。
验证在所有可能引起异常的 JNI 调用之前都检测了异常。
确保所有 Get/Release 调用在各 JNI 方法中都是相匹配的。
IBM 的 JVM 实现包括开启自动 JNI 检测的选项,其代价是较慢的执行速度。与出色的代码单元测试相结合,这是一种极为强大的工具。您可以运行应用程序或单元测试来执行遵从性检查,或者确定所遇到的 bug 是否是由本机引起的。除了执行上述规范遵从性检查之外,它还能确保:
传递给 JNI 方法的参数属于正确的类型。
JNI 代码未读取超过数组结束部分之外的内容。
传递给 JNI 方法的指针都是有效的。
JNI 检测报告的所有结论并不一定都是代码中的错误。它们还包括一些针对代码的建议,您应该仔细阅读它们以确保代码功能正常。
您可以通过以下命令行启用 JNI 检测选项:
Usage: -Xcheck:jni:[option[,option[,...]]] all check application and system classes verbose trace certain JNI functions and activities trace trace all JNI functions nobounds do not perform bounds checking on strings and arrays nonfatal do not exit when errors are detected nowarn do not display warnings noadvice do not display advice novalist do not check for va_list reuse valist check for va_list reuse pedantic perform more thorough, but slower checks help print this screen |