转自 : 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() 来获取 JNIEnvJavaVM 对象本身可以通过使用 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大正确性缺陷及优化技巧(注意是正确性缺陷)的更多相关文章

  1. NDK(20)JNI的5大性能缺陷及优化技巧

    转自 : http://www.ibm.com/developerworks/cn/java/j-jni/index.html JNI 编程缺陷可以分为两类: 性能:代码能执行所设计的功能,但运行缓慢 ...

  2. 网站入住各大搜索引擎的seo优化技巧

    最近在公司上班的时候做了一个工业物联网的项目,上层主管提出要求,让这个网站入住各大搜索引擎,也就是说在各大搜索引擎中输入与网站相关的关键字就能搜索到我们自己的网站.刚开始自己一脸懵逼,因为之前自己并没 ...

  3. java+Mysql大数据的一些优化技巧

    众所周知,java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,在做数据处理中,我们常见的手段是分解,压缩,并行,临时文件等方法; 例如,我们要将 ...

  4. Android游戏开发实践(1)之NDK与JNI开发02

    Android游戏开发实践(1)之NDK与JNI开发02 承接上篇Android游戏开发实践(1)之NDK与JNI开发01分享完JNI的基础和简要开发流程之后,再来分享下在Android环境下的JNI ...

  5. [ 转载 ] Android JNI(一)——NDK与JNI基础

    Android JNI(一)——NDK与JNI基础 隔壁老李头 关注  4.4 2018.05.09 17:15* 字数 5481 阅读 11468评论 8喜欢 140 本系列文章如下: Androi ...

  6. Android JNI(一)——NDK与JNI基础

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  7. Android(安卓)开发通过NDK调用JNI,使用opencv做本地c++代码开发配置方法 边缘检测 范例代码

    以前写过两个Android开发配置文档,使用NDK进行JNI开发,这样能够利用以前已经写好的C++代码. 前两篇博客地址: http://blog.csdn.net/watkinsong/articl ...

  8. Android游戏开发实践(1)之NDK与JNI开发03

    Android游戏开发实践(1)之NDK与JNI开发03 前面已经分享了两篇有关Android平台NDK与JNI开发相关的内容.以下列举前面两篇的链接地址,感兴趣的可以再回顾下.那么,这篇继续这个小专 ...

  9. Android游戏开发实践(1)之NDK与JNI开发01

    Android游戏开发实践(1)之NDK与JNI开发01 NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码&q ...

随机推荐

  1. 折腾了好久的macos+apache+php+phpmyadmin 终于成功了!

    由于最近需要布置mantis用来进行bug追踪,在此记录其过程. 由于PHP apache环境在Mac OS上是自带的,所以不需要另处下安装包,只需要简单配置一下即可. 首先打开终端输入命令: sud ...

  2. Asp.Net MVC使用ViewData导致双引号被转义的解决方法

    使用以下方法进行输出 @Html.Raw(ViewData["jsonString"].ToString())

  3. iOS10 关于推送-b

    最近在研究iOS10关于推送的新特性, 相比之前确实做了很大的改变,总结起来主要是以下几点: 推送内容更加丰富,由之前的alert 到现在的title, subtitle, body 推送统一由tri ...

  4. php多条件查询

    $sql)"; if(!empty($uid)) { $sql .=" and uid= ".$uid; } if(!empty($time1) && e ...

  5. php 安全处理方案

    Safe::mysqlSafe(); sql注入,升级5.3.6以上版本php 方案一:将所有请求中所有数据(get/post/cookie)实现mysql_escape_string进行安全处理. ...

  6. 使用Forms进行身份验证(Asp.net)

    1.背景      以往项目登陆后的用户信息都是存放在session中,但session有一个问题就是读取的时候需要先实例化所在类,在调用对象()如果用static修饰,则可能到时多次登陆sessio ...

  7. 树形dp求树的重心

    Balancing Act http://poj.org/problem?id=1655 #include<cstdio> #include<cstring> #include ...

  8. Eclipse插件开发 swt ComboBoxCellEditor CCombo 下拉框高度

    效果图:     代码如下 bindingPageTableViewer.setCellModifier(new ICellModifier() { public boolean canModify( ...

  9. TCP 滑动窗口和 拥塞窗口

    转http://coolshell.cn/articles/11609.html 滑动窗口 -- 表征发送端和接收端的接收能力 拥塞窗口-- 表征中间设备的传输能力 TCP滑动窗口 需要说明一下,如果 ...

  10. Zabbix 集成 OneAlert 实现全方位告警

    1. 前言 告警将重要信息发送给运维「或者其他相关人」,及时发现并且处理问题.在所有开源监控软件里面,Zabbix 的告警方式无疑是最棒的.告警的方式各式各样,从 Email 告警到飞信.139/18 ...