NDK(22)JNI编程如何避免常见缺陷
转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html
避免常见缺陷
假设您编写了一些新 JNI 代码,或者继承了别处的某些 JNI 代码,如何才能确保避免了常见缺陷,或者在继承代码中发现它们?表 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 |
您可以在开发周期的早期确定许多常见缺陷,方法如下:
根据 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
使用 IBM JVM 的 -Xcheck:jni
选项作为标准开发流程的一部分可以帮助您更加轻松地找出代码错误。特别是,它可以帮助您确定在错误线程中使用 JNIEnv
以及未正确使用关键区域的缺陷的根源。
最新的 Sun JVM 提供了一个类似的 -Xcheck:jni
选项。它的工作原理不同于 IBM 版本,并且提供了不同的信息,但是它们的作用是相同的。它会在发现未符合规范的代码时发出警告,并且可以帮助您确定常见的 JNI 缺陷。
分析方法跟踪
生成对已调用本机方法以及这些本机方法发起的 JNI 回调的跟踪,这对确定大量常见缺陷的根源是非常有用的。可确定的问题包括:
- 大量
GetFieldID()
和GetMethodID()
调用 — 特别是,如果这些调用针对相同的字段和方法 — 表示字段和方法未被缓存。 GetTypeArrayElements()
调用实例(而非GetTypeArrayRegion()
)有时表示存在不必要的复制。- 在 Java 代码与本机代码之前来回快速切换(由时间戳指示)有时表示 Java 代码与本机代码之间的界限有误,从而造成性能较差。
- 每个本机函数调用后面都紧接着大量
GetFieldID()
调用,这种模式表示并未传递所需的参数,而是强制本机回访完成工作所需的数据。 - 调用可能抛出异常的 JNI 方法之后缺少对
ExceptionOccurred()
或ExceptionCheck()
的调用表示本机未正确检测异常。 GetXXX()
和ReleaseXXX()
方法调用的数量不匹配表示缺少释放操作。- 在
GetXXXCritical()
和ReleaseXXXCritical()
调用之间调用 JNI 方法表示未遵循规范施加的限制。 - 如果调用
GetXXXCritical()
和ReleaseXXXCritical()
之间相隔的时间较长,则表示未遵循 “不要阻塞调用” 规范所施加的限制。 NewGlobalRef()
和DeleteGlobalRef()
调用之间出现严重失衡表示释放不再需要的引用时出现故障。
一些 JVM 实现提供了一种可用于生存方法跟踪的机制。您还可以通过各种外部工具来生成跟踪,比如探查器和代码覆盖工具。
IBM JVM 实现提供了许多用于生成跟踪信息的方法。第一种方法是使用 -Xcheck:jni:trace
选项。这将生成对已调用的本机方法以及它们发起的 JNI 回调的跟踪。清单 13 显示某个跟踪的摘录(为便于阅读,隔开了某些行):
清单 13. IBM JVM 实现所生成的方法跟踪
Call JNI: java/lang/System.getPropertyList()[Ljava/lang/String; {
00177E00 Arguments: void
00177E00 FindClass("java/lang/String")
00177E00 FindClass("com/ibm/oti/util/Util")
00177E00 Call JNI: com/ibm/oti/vm/VM.useNativesImpl()Z {
00177E00 Arguments: void
00177E00 Return: (jboolean)false
00177E00 }
00177E00 Call JNI: java/security/AccessController.initializeInternal()V {
00177E00 Arguments: void
00177E00 FindClass("java/security/AccessController")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedAction;)Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedExceptionAction;)Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedAction;Ljava/security/AccessControlContext;)
Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedExceptionAction;
Ljava/security/AccessControlContext;)Ljava/lang/Object;")
00177E00 Return: void
00177E00 }
00177E00 GetStaticMethodID(com/ibm/oti/util/Util, "toString",
"([BII)Ljava/lang/String;")
00177E00 NewByteArray((jsize)256)
00177E00 NewObjectArray((jsize)118, java/lang/String, (jobject)NULL)
00177E00 SetByteArrayRegion([B@0018F7D0, (jsize)0, (jsize)30, (void*)7FF2E1D4)
00177E00 CallStaticObjectMethod/CallStaticObjectMethodV(com/ibm/oti/util/Util,
toString([BII)Ljava/lang/String;, (va_list)0007D758) {
00177E00 Arguments: (jobject)0x0018F7D0, (jint)0, (jint)30
00177E00 Return: (jobject)0x0018F7C8
00177E00 }
00177E00 ExceptionCheck()
清单 13 中的跟踪摘录显示了已调用的本机方法(比如 AccessController.initializeInternal()V
)以及本机方法发起的 JNI 回调。
使用 -verbose:jni
选项
Sun 和 IBM JVM 还提供了一个 -verbose:jni
选项。对于 IBM JVM 而言,开启此选项将提供关于当前 JNI 回调的信息。清单 14 显示了一个示例:
清单 14. 使用 IBM JVM 的 -verbose:jni
列出 JNI 回调
<JNI GetStringCritical: buffer=0x100BD010>
<JNI ReleaseStringCritical: buffer=100BD010>
<JNI GetStringChars: buffer=0x03019C88>
<JNI ReleaseStringChars: buffer=03019C88>
<JNI FindClass: java/lang/String>
<JNI FindClass: java/io/WinNTFileSystem>
<JNI GetMethodID: java/io/WinNTFileSystem.<init> ()V>
<JNI GetStaticMethodID: com/ibm/j9/offload/tests/HelloWorld.main ([Ljava/lang/String;)V>
<JNI GetMethodID: java/lang/reflect/Method.getModifiers ()I>
<JNI FindClass: java/lang/String>
对于 Sun JVM 而言,开启 -verbose:jni
选项不会提供关于当前调用的信息,但它会提供关于所使用的本机方法的额外信息。清单 15 显示了一个示例:
清单 15. 使用 Sun JVM 的 -verbose:jni
[Dynamic-linking native method java.util.zip.ZipFile.getMethod ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.initIDs ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.init ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.inflateBytes ... JNI]
[Dynamic-linking native method java.util.zip.ZipFile.read ... JNI]
[Dynamic-linking native method java.lang.Package.getSystemPackage0 ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.reset ... JNI]
开启此选项还会让 JVM 针对使用过多本地引用而未通知 JVM 的情况发起警告。举例来说,IBM JVM 生成了这样一个消息:
JVMJNCK065W JNI warning in FindClass: Automatically grew local reference frame capacity
from 16 to 48. 17 references are in use.
Use EnsureLocalCapacity or PushLocalFrame to explicitly grow the frame.
虽然 -verbose:jni
和 -Xcheck:jni:trace
选项可帮助您方便地获取所需的信息,但手动审查此信息是一项艰巨的任务。一个不错的提议是,创建一些脚本或实用工具来处理由 JVM 生成的跟踪文件,并查看 警告。
生成转储
运行中的 Java 进程生成的转储包含大量关于 JVM 状态的信息。对于许多 JVM 来说,它们包括关于全局引用的信息。举例来说,最新的 Sun JVM 在转储信息中包括这样一行:
JNI global references: 73
通过生成前后转储,您可以确定是否创建了任何未正常释放的全局引用。
您可以在 UNIX® 环境中通过对 java
进程发起 kill -3
或 kill -QUIT
来请求转储。在 Windows® 上,使用 Ctrl+Break 组合键。
对于 IBM JVM,使用以下步骤获取关于全局引用的信息:
- 将
-Xdump:system:events=user
添加到命令行。这样,当您在 UNIX 系统上调用kill -3
或者在 Windows 上按下 Ctrl+Break 时,JVM 便会生成转储。 - 程序在运行中时会生成后续转储。
- 运行
jextract -nozip core.XXX output.xml
,这将会将转储信息提取到可读格式的 output.xml 中。 - 查找 output.xml 中的
JNIGlobalReference
条目,它提供关于当前全局引用的信息,如清单 16 所示:
清单 16. output.xml 中的 JNIGlobalReference
条目
<rootobject type="Thread" id="0x10089990" reachability="strong" />
<rootobject type="Thread" id="0x10089fd0" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x100100c0" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011250" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011840" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011880" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10010af8" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10010360" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10081f48" reachability="strong" />
<rootobject type="StringTable" id="0x10010be0" reachability="weak" />
<rootobject type="StringTable" id="0x10010c70" reachability="weak" />
<rootobject type="StringTable" id="0x10010d00" reachability="weak" />
<rootobject type="StringTable" id="0x10011018" reachability="weak" />
通过查看后续 Java 转储中报告的数值,您可以确定全局引用是否出现的泄漏。
参见 参考资料 获取关于使用转储文件以及 IBM JVM 的 jextract
的更多信息。
执行代码审查
代码审查经常可用于确定常见缺陷,并且可以在各种级别上完成。继承新代码时,快速扫描可以发现各种问题,从而避免稍后花费更多时间进行调试。在某些情况下,审查是确定缺陷实例(比如未检查返回值)的唯一方法。举例来说,此代码的问题可能可以通过代码审查轻松确定,但却很难通过调试来发现:
int calledALot(JNIEnv* env, jobject obj, jobject allValues){
jclass cls = (*env)->GetObjectClass(env,allValues);
jfieldID a = (*env)->GetFieldID(env, cls, "a", "I");
jfieldID b = (*env)->GetFieldID(env, cls, "b", "I");
jfieldID c = (*env)->GetFieldID(env, cls, "c", "I");
jfieldID d = (*env)->GetFieldID(env, cls, "d", "I");
jfieldID e = (*env)->GetFieldID(env, cls, "e", "I");
jfieldID f = (*env)->GetFieldID(env, cls, "f", "I"); } jclass getObjectClassHelper(jobject object){
/* use globally cached JNIEnv */
return cls = (*globalEnvStatic)->GetObjectClass(globalEnvStatic,allValues);
}
代码审查可能会发现第一个方法未正确缓存字段 ID,尽管重复使用了相同的 ID,并且第二个方法所使用的 JNIEnv
并不在应该在的线程上。
NDK(22)JNI编程如何避免常见缺陷的更多相关文章
- NDK(21)JNI的5大正确性缺陷及优化技巧(注意是正确性缺陷)
转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html JNI 编程缺陷可以分为两类: 性能:代码能执行所设计的功能,但运行缓慢 ...
- NDK(20)JNI的5大性能缺陷及优化技巧
转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html JNI 编程缺陷可以分为两类: 性能:代码能执行所设计的功能,但运行缓慢 ...
- Android studio 使用NDK工具实现JNI编程
前言: Android开发中常常会使用到第三方的.so库.在使用.so库的时候就要用到JNI编程.JNI是Java Native Interface的缩写.它提供了若干的API实现了Java和其它语言 ...
- [ 转载 ] Android JNI(一)——NDK与JNI基础
Android JNI(一)——NDK与JNI基础 隔壁老李头 关注 4.4 2018.05.09 17:15* 字数 5481 阅读 11468评论 8喜欢 140 本系列文章如下: Androi ...
- Android JNI(一)——NDK与JNI基础
本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...
- Android studio 下JNI编程实例并生成so库
Android studio 下JNI编程实例并生成so库 因为公司需要为Android相机做美颜等图像后期处理,需要使用JNI编程,最近学了下JNI,并且在Android Studio下实现了一个小 ...
- Android中JNI编程的那些事儿(1)
转:Android中JNI编程的那些事儿(1)http://mobile.51cto.com/android-267538.htm Android系统不允许一个纯粹使用C/C++的程序出现,它要求必须 ...
- 【转】Android JNI编程—JNI基础
原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它 ...
- android windows 上JNI编程
昨天学习windows上的JNI编程,JNI说白了就是java和c语言的一个互相沟通的桥梁.java能够调用JNI来完毕调用C语言实现的方法. JNI的全称是(Java native interfac ...
随机推荐
- mysql 连接语句
在 SELECT 语句中,如果 FROM 子句引用了多个表源或视图,可以使用 JOIN 指示指定的联接操作应在指定的表源或视图之间执行. 一.交叉联接:CROSS JOIN 交叉联接将执行一个叉积(迪 ...
- linux 命令grep
linux 命令grep grep命令用来搜索文本,或从给定的文件中搜索行内包含了给定字符串或单词的文件.通常来说,grep显示匹配的行.使用grep来搜索包括一个或多个正则表达式匹配到的文本行,然后 ...
- 树形动规--没有上司的舞会--C++
题目来源:code[VS] 题目描述 Description Ural大学有N个职员,编号为1~N.他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.每个职员有一个 ...
- How to: Add SharePoint 2010 Search Web Parts to Web Part Gallery for Upgraded Site Collections
When you upgrade to Microsoft SharePoint Server 2010, some of the new SharePoint Enterprise Search W ...
- 【原创】jQuery插件 - Booklet翻书特效教程(一) 一般设置
jQuery插件 - Booklet翻书特效教程(一) 一般设置 本文由五月雨恋提供,转载请注明出处. 一.宽高(width/height) 1.自定义大小 $(function(){ // 自定义页 ...
- windows下SSH客户端远程访问Linux出现错误
- cocos2dx中的实现地图卷动的两种方式
在游戏当中,实现地图卷动是最基本的功能,具体的实现的方法,大致有两类: 方法一:加载两张图片,轮流显示, 优点: 1.无论是地图上下卷动,还是左右卷动都可以 2.支持各种图片,(png,jpg...) ...
- C#调用PowerShell的经历
好久没有写程序了, 再次上手也处于功能强大的Windows PowerShell的缘故. 不多话, 先上段代码引入正题.... static Collection<PSObject> Ru ...
- Javascript数据类型之Undefined和null
Javascrip中的数据类型分为原始数据类型(primitive type)和对象数据类型(object type). 原始数据类型 原始数据类型包括:数字.字符串.布尔值.null.undefin ...
- C#的配置文件App.config使用总结 - 转
http://blog.csdn.net/celte/article/details/9749389 首先,先说明,我使用的app.config 配置文件的格式如下: <?xml version ...