NDK(21)JNI的5大正确性缺陷及优化技巧(注意是正确性缺陷)
转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html
JNI 编程缺陷可以分为两类:
- 性能:代码能执行所设计的功能,但运行缓慢或者以某种形式拖慢整个程序。
- 正确性:代码有时能正常运行,但不能可靠地提供所需的功能;最坏的情况是造成程序崩溃或挂起。
正确性缺陷
5 大 JNI 正确性缺陷包括:
1.使用错误的 JNIEnv
执行本机代码的线程使用 JNIEnv 发起 JNI 方法调用。但是,JNIEnv 并不是仅仅用于分派所请求的方法。JNI 规范规定每个 JNIEnv 对于线程来说都是本地的。JVM 可以依赖于这一假设,将额外的线程本地信息存储在 JNIEnv 中。一个线程使用另一个线程中的 JNIEnv 会导致一些小 bug 和难以调试的崩溃问题。
线程可以通过 JavaVM 对象的接口 GetEnv() 来获取 JNIEnv。JavaVM 对象本身可以通过使用 JNIEnv 方法调用 JNI GetJavaVM() 来获取,并且可以被缓存以及跨线程共享。缓存 JavaVM 对象的副本将允许任何能访问缓存对象的线程在必要时获取对它自己的 JNIEnv 访问。要实现最优性能,线程应该绕过 JNIEnv,因为查找它有时会需要大量的工作。
正确性技巧 #1
仅在相关的单一线程中使用 JNIEnv。
2.未正确使用全局引用
本机可以创建一些全局引用,以保证对象在不再需要时才会被垃圾收集器回收。常见的缺陷包括忘记删除已创建的全局引用,或者完全失去对它们的跟踪。考虑一个本机创建了全局引用,但是未删除它或将它存储在某处:
lostGlobalRef(JNIEnv* env, jobject obj, jobject keepObj) {
jobject gref = (*env)->NewGlobalRef(env, keepObj);
}
创建全局引用时,JVM 会将它添加到一个禁止垃圾收集的对象列表中。当本机返回时,它不仅会释放全局引用,应用程序还无法获取引用以便稍后释放它 — 因此,对象将会始终存在。不释放全局引用会造成各种问题,不仅因为它们会保持对象本身为活动状态,还因为它们会将通过该对象能接触到的所有对象都保持为活动状态。在某些情况下,这会显著加剧内存泄漏。
正确性技巧 #6
始终跟踪全局引用,并确保不再需要对象时删除它们。
3.未检测异常
本机能调用的许多 JNI 方法都会引起与执行线程相关的异常。当 Java 代码执行时,这些异常会造成执行流程发生变化,这样便会自动调用异常处理代码。当某个本机方法调用某个 JNI 方法时会出现异常,但检测异常并采用适当措施的工作将由本机来完成。一个常见的 JNI 缺陷是调用 JNI 方法而未在调用完成后测试异常。这会造成代码有大量漏洞以及程序崩溃。
举例来说,考虑调用 GetFieldID() 的代码,如果无法找到所请求的字段,则会出现 NoSuchFieldError。如果本机代码继续运行而未检测异常,并使用它认为应该返回的字段 ID,则会造成程序崩溃。举例来说,如果 Java 类经过修改,导致 charField 字段不再存在,则清单 10 中的代码可能会造成程序崩溃 — 而不是抛出一个 NoSuchFieldError:
清单 10. 未能检测异常
jclass objectClass;
jfieldID fieldID;
jchar result = ; objectClass = (*env)->GetObjectClass(env, obj);
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
result = (*env)->GetCharField(env, obj, fieldID);
添加异常检测代码要比在事后尝试调试崩溃简单很多。经常,您只需要检测是否出现了某个异常,如果是则立即返回 Java 代码以便抛出异常。然后,使用常规的 Java 异常处理流程处理它或者显示它。举例来说,清单 11 将检测异常:
清单 11. 检测异常
jclass objectClass;
jfieldID fieldID;
jchar result = ; objectClass = (*env)->GetObjectClass(env, obj);
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
if ((*env)->ExceptionOccurred(env)) {
return;
}
result = (*env)->GetCharField(env, obj, fieldID);
不检测和清除异常会导致出现意外行为。您可以确定以下代码的问题吗?
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
if (fieldID == NULL) {
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "D");
}
return (*env)->GetIntField(env, obj, fieldID);
问题在于,尽管代码处理了初始 GetFieldID() 未返回字段 ID 的情况,但它并未清除 此调用将设置的异常。因此,本机返回的结果会造成立即抛出一个异常。
正确性技巧 #2
在发起可能会导致异常的 JNI 调用后始终检测异常。
4.未检测返回值
许多 JNI 方法都通过返回值来指示调用成功与否。与未检测异常相似,这也存在一个缺陷,即代码未检测返回值却假定调用成功而继续运行。对于大多数 JNI 方法来说,它们都设置了返回值和异常状态,这样应用程序更可以通过检测异常状态或返回值来判断方法运行正常与否。
您可以确定以下代码的问题吗?
clazz = (*env)->FindClass(env, "com/ibm/j9//HelloWorld");
method = (*env)->GetStaticMethodID(env, clazz, "main","([Ljava/lang/String;)V");
(*env)->CallStaticVoidMethod(env, clazz, method, NULL);
问题在于,如果未发现 HelloWorld 类,或者如果 main() 不存在,则本机将造成程序崩溃。
正确性技巧 #3
始终检测 JNI 方法的返回值,并包括用于处理错误的代码路径。
5.未正确使用数组方法
GetXXXArrayElements() 和 ReleaseXXXArrayElements() 方法允许您请求任何元素。同样,GetPrimitiveArrayCritical()、ReleasePrimitiveArrayCritical()、GetStringCritical() 和 ReleaseStringCritical()允许您请求数组元素或字符串字节,以最大限度降低直接指向数组或字符串的可能性。这些方法的使用存在两个常见的缺陷。
其一,忘记在ReleaseXXX() 方法调用中提供更改。即便使用 Critical 版本,也无法保证您能获得对数组或字符串的直接引用。一些 JVM 始终返回一个副本,并且在这些 JVM 中,如果您在 ReleaseXXX() 调用中指定了 JNI_ABORT,或者忘记调用了 ReleaseXXX(),则对数组的更改不会被复制回去。
举例来说,考虑以下代码:
void modifyArrayWithoutRelease(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy);
if ((*env)->ExceptionCheck(env)){
return;
}
buffer[] = ;
}
在提供直接指向数组的指针的 JVM 上,该数组将被更新;但是,在返回副本的 JVM 上则不是如此。这会造成您的代码在一些 JVM 上能够正常运行,而在其他 JVM 上却会出错。您应该始终始终包括一个释放(release)调用,如清单 12 所示:
清单 12. 包括一个释放调用
void modifyArrayWithRelease(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy);
if ((*env)->ExceptionCheck(env))
return;
buffer[] = ;
(*env)->ReleaseByteArrayElements(env, arr1, buffer, JNI_COMMIT);
if ((*env)->ExceptionCheck(env))
return;
}
正确性技巧 #4
不要忘记为每个 GetXXX() 使用模式 (复制回去并释放内存)调用 ReleaseXXX()。
第二个缺陷是不注重规范对在 GetXXXCritical() 和 ReleaseXXXCritical() 之间施加的限制。本机可能不会在这些方法之间发起任何调用,并且可能不会由于任何原因而阻塞。未重视这些限制会造成应用程序或 JVM 中出现间断性死锁.
举例来说,以下代码看上去可能没有问题
void workOnPrimitiveArray(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)->GetPrimitiveArrayCritical(env, arr1, &isCopy);
if ((*env)->ExceptionCheck(env))
return;
processBufferHelper(buffer);
(*env)->ReleasePrimitiveArrayCritical(env, arr1, buffer, );
if ((*env)->ExceptionCheck(env))
return;
}
但是,我们需要验证在调用 processBufferHelper() 时可以运行的所有代码都没有违反任何限制。这些限制适用于在 Get 和 Release 调用之间执行的所有代码,无论它是不是本机的一部分。
正确性技巧 #5
确保代码不会在 GetXXXCritical() 和 ReleaseXXXCritical() 调用之间发起任何 JNI 调用或由于任何原因出现阻塞。
NDK(21)JNI的5大正确性缺陷及优化技巧(注意是正确性缺陷)的更多相关文章
- NDK(20)JNI的5大性能缺陷及优化技巧
转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html JNI 编程缺陷可以分为两类: 性能:代码能执行所设计的功能,但运行缓慢 ...
- 网站入住各大搜索引擎的seo优化技巧
最近在公司上班的时候做了一个工业物联网的项目,上层主管提出要求,让这个网站入住各大搜索引擎,也就是说在各大搜索引擎中输入与网站相关的关键字就能搜索到我们自己的网站.刚开始自己一脸懵逼,因为之前自己并没 ...
- java+Mysql大数据的一些优化技巧
众所周知,java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,在做数据处理中,我们常见的手段是分解,压缩,并行,临时文件等方法; 例如,我们要将 ...
- Android游戏开发实践(1)之NDK与JNI开发02
Android游戏开发实践(1)之NDK与JNI开发02 承接上篇Android游戏开发实践(1)之NDK与JNI开发01分享完JNI的基础和简要开发流程之后,再来分享下在Android环境下的JNI ...
- [ 转载 ] 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(安卓)开发通过NDK调用JNI,使用opencv做本地c++代码开发配置方法 边缘检测 范例代码
以前写过两个Android开发配置文档,使用NDK进行JNI开发,这样能够利用以前已经写好的C++代码. 前两篇博客地址: http://blog.csdn.net/watkinsong/articl ...
- Android游戏开发实践(1)之NDK与JNI开发03
Android游戏开发实践(1)之NDK与JNI开发03 前面已经分享了两篇有关Android平台NDK与JNI开发相关的内容.以下列举前面两篇的链接地址,感兴趣的可以再回顾下.那么,这篇继续这个小专 ...
- Android游戏开发实践(1)之NDK与JNI开发01
Android游戏开发实践(1)之NDK与JNI开发01 NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码&q ...
随机推荐
- JNI-入门之一
下面我们开始编写HelloWorld程序,由于涉及到要编写c/c++代码因此我们会在开发中使用Microsoft VC++工具. 编写java代码我们在硬盘上建立一个hello目录作为我们的工作目录, ...
- iTween基础之Rotate(旋转角度)
一.基础介绍:二.基础属性 原文地址 :http://blog.csdn.net/dingkun520wy/article/details/50696489 一.基础介绍 RotateTo:旋转游戏物 ...
- 【BZOJ 2245】[SDOI2011]工作安排
Description 你的公司接到了一批订单.订单要求你的公司提供n类产品,产品被编号为1~n,其中第i类产品共需要Ci件.公司共有m名员工,员工被编号为1~m员工能够制造的产品种类有所区别.一件产 ...
- ADT通过svn进行团队开发,svn插件不好使的解决方案
在使用ADT的svn插件的时候老是会出现各种异常,所以就干脆不用svn插件了,直接将adt的工作空间建在svn上面,以保证团队成员共用一套代码,节约宝贵的整合时间. 使用步骤: 1.首先需要安装好sv ...
- Netsharp快速入门(之12) 销售管理(开发发货单工作区)
作者:秋时 杨昶 时间:2014-02-15 转载须说明出处 4.4 发货单 4.4.1 部件工作区设置 1.设置部件工作区,需要设置的部件如下 2.设置单据和明细列表的字段,设置完成 ...
- DataTable.Compute 性能慢的问题
问题描述 工作中碰到一个dt.Compute("max(lineid)","")来取最大行号的情况,由于dt中数据大概有4000条,发现每次调用需要0.3秒的耗 ...
- DataGrid行详细信息的绑定--DataGrid.RowDetailsTe(转载)
在Silverlight中的DataGrid控件使用中我们想点击其中一行并且看这一行的详细信息应该如何做呢?而且这个详细信息是多行的数据,而非简单的几个属性. 在这里我们使用DataGrid.RowD ...
- PHP中发送邮件的几种方法总结
1. 使用 mail() 函数 没什么好讲的,就是使用系统自带的smtp系统来发送,一般是使用sendmail来发.这个按照各个系统不同而定.使用参考手册. 2. 使用管道的形式 昨天刚测试成功,使用 ...
- POJ1860Currency Exchange(SPFA)
http://poj.org/problem?id=1860 题意: 题目中主要是说存在货币兑换点,然后现在手里有一种货币,要各种换来换去,最后再换回去的时候看能不能使原本的钱数增多,每一种货币都有 ...
- poj 3358 Period of an Infinite Binary Expansion
由乘2取整得到分数的小数位,可以找到规律!!! 例如:1/10,2/10,4/10,8/10,16/10,32/10,64/10…… 取整后:1/10,2/10,4/10,8/10,6/10,2/10 ...