转自 : 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. Hashmap in java

    1. HashMap要点: 1.1 基本数据结构:  采用 数组+链表/平衡二叉查找树 的组合形式,所有键值对都以Entry<K,V>形式存储(每put进一个键值对,就会实例化一个Entr ...

  2. c语言关于二进制的输出

    c语言中的二进制输出是没有占位符的,不像八进制:%o: 和十六进制:x%: c中二进制的输出 //右移31位,从最高为开始和1做&运算,得到每一位的二进制数值 void printbinry( ...

  3. 让站长在SEO时更得心应手的六个细节

    分析一下SEO的心得,希望能对很多做网站优化和做网站推广的站长有帮助,现在做SEO的人越来网站推广越多,即使很多人在做,但不知道自己能否成功,自己的优化是否会见效,所以在这里还是来分享一下咱们在SEO ...

  4. 2014 Multi-University Training Contest 7

    官方解题报告:http://blog.sina.com.cn/s/blog_a19ad7a10102uzd4.html Magical Forest http://acm.hdu.edu.cn/sho ...

  5. POJ 1906

    #include <iostream> #include <string> #define MAXN 150 using namespace std; int _m[MAXN] ...

  6. C#画图

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Dr ...

  7. POJ 1691 Painting A Board(DFS)

    链接 题意 : 看了好长时间终于看懂题目了,将一个大矩形划分成若干小矩形,告诉你每个小矩形的左上角那个点和右下角那个点的坐标,告诉你这个小矩形要涂的颜色,每个颜色对应一个刷子,问你最少要使用几次刷子. ...

  8. Selenium--cssselector

        CSS(即层叠样式表Cascading Stylesheet) Selector来定位(locate)页面上的元素(Elements).Selenium官网的Document里极力推荐使用CS ...

  9. hdu1116

    http://acm.hdu.edu.cn/showproblem.php?pid=1116 #include<stdio.h> #include<math.h> #inclu ...

  10. NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

    解决办法: http://stackoverflow.com/questions/4037125/namespace-err-an-attempt-is-made-to-create-or-chang ...