JNI 对象在函数调用中的生命周期
2016-04-19 09:21
239 查看
http://www.ibm.com/developerworks/cn/java/j-lo-jni/
这个文字说了从java弄到c++中的对象生命周期问题。
Java 本地编程接口(Java Native Interface,JNI) 易懂且容易上手,但对于一些新手甚至于一些中级开发者,在编程中都没有把 JNI 对象的生命周期考虑在内。以至于经常碰见莫名的 Crash,或者 segmentation fault。在花费了相当精力去调试后,才发现是 JNI 对象使用不当,当初保存的 JNI 对象已经被系统回收,却仍试图去使用之。本文结合实际开发中遇到的此类问题,讲述和强调了 JNI 对象生命周期的概念,并提出正确的解决方法,希望能和读者分享自己的经验和教训。
0
评论
恽
益群 (yunyiqun@cn.ibm.com),
软件工程师, IBM
2007 年 12 月 06 日
内容
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
开始您的试用
在 JNI 编程中常需要从一个普通的 C/C++ 函数中调用 JNI 方法,比如:
C 函数,目的是把参数中的键盘事件交给 Java 代码处理。它并没有 JNI 参数,却需要调用一个 JNI 方法。那么调用所必需的参数
Java 的函数,处理了这个键盘事件。在这种情况下,设计者很清楚,也深信
Native 函数返回前被调用。对应的场景代码应该是这样的:
但万一
crash 或者 segmentation fault 这类错误则几乎是一定的。事实上,如上设计,
回页首
调用者本来以为这个函数能帮他的大忙,现在却疑惑了,到底发生了什么?做错了什么呢?
原因其实很简单,上面三个全局变量中的
这里要澄清的是
jobject,所以只要它对应的 class 没有被卸载,在退出
thread 来说,它是唯一的,所以只要是
thread,初始化后它的值在这个 thread 没有中止之前,都一直是合法的。
那如何才能解决
这首先涉及到 Java 和 Native 代码之间函数调用时,参数如何传递的问题。简单类型,也就是内置类型,比如 int, char 等是值传递(pass by value),而其它 Java 对象都是引用传递(pass by reference),这些对象引用由 JVM 传给 Native 代码,每个都有其生命周期。
其次,Java 对象的生命周期是由它的引用类型决定的,这里的引用分两种:local reference 和 global reference。Native 函数参数中 jobject 或者它的子类,其参数都是 local reference。Local reference 只在这个 Native 函数中有效,Native 函数返回后,引用的对象就被释放,它的生命周期就结束了。若要留着日后使用,则需根据这个 local reference 创建 global reference。Global reference
不会被系统自动释放,它仅当被程序明确调用 DeleteGlobalReference 时才被回收。
回页首
于是解决的办法就出来了:
在
这样,无论
回页首
若要在某个 Native 代码返回后,还希望能继续使用 JVM 提供的参数(比如 init 函数中的 jclass), 或者是过程中调用 JNI 函数的返回值(比如 g_mid),只要它是一个 jobject, 则需要为它创建一个 global reference,以后只能使用这个 global reference;若不是一个 jobject,则无需这么做。
jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。JNIEnv 对于每个 thread 而言是唯一的,不能也不需要对它调用 NewGlobalReference。
这个文字说了从java弄到c++中的对象生命周期问题。
JNI 对象在函数调用中的生命周期
Java 本地编程接口(Java Native Interface,JNI) 易懂且容易上手,但对于一些新手甚至于一些中级开发者,在编程中都没有把 JNI 对象的生命周期考虑在内。以至于经常碰见莫名的 Crash,或者 segmentation fault。在花费了相当精力去调试后,才发现是 JNI 对象使用不当,当初保存的 JNI 对象已经被系统回收,却仍试图去使用之。本文结合实际开发中遇到的此类问题,讲述和强调了 JNI 对象生命周期的概念,并提出正确的解决方法,希望能和读者分享自己的经验和教训。0
评论
恽
益群 (yunyiqun@cn.ibm.com),
软件工程师, IBM
2007 年 12 月 06 日
内容
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
开始您的试用
问题
在 JNI 编程中常需要从一个普通的 C/C++ 函数中调用 JNI 方法,比如:long ProcessKeyboardEventInJava(int keyboardEvent) { long error = 0; JNIEnv* env = g_env; jclass that = g_class; jmethodID mid = g_mid; … … error = env->CallStaticIntMethod(that, mid, keyboardEvent); return error; }
ProcessKeyboardEventInJava只是个普通的
C 函数,目的是把参数中的键盘事件交给 Java 代码处理。它并没有 JNI 参数,却需要调用一个 JNI 方法。那么调用所必需的参数
JNIEnv,
jclass和
jmethodID,是如何获得呢?从上面的代码中可以看出,它们来自保存的全局变量
g_env,
g_class和
g_mid。这样做对么?安全么?也许这段代码在某种情况下,实现了软件的需求,成功地执行了
Java 的函数,处理了这个键盘事件。在这种情况下,设计者很清楚,也深信
ProcessKeyboardEventInJava只会在某个
Native 函数返回前被调用。对应的场景代码应该是这样的:
JNIEnv * g_env; jclass g_class; jmethodID g_mid; JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls) { g_class = cls; g_env = env; g_mid = env->GetStaticMethodID(cls, "ProcessKeyboardEventInJava", "(III)I"); while(true) { …. long error; error = ProcessKeyboardEventInJava(theEvent) …. } }
但万一
ProcessKeyboardEventInJava在
Java_SampleTest_init(以下简称
init)返回后的其他时机被调用了,会怎样呢?那时可就没有这么幸运了,初衷是肯定达不到了,而发生
crash 或者 segmentation fault 这类错误则几乎是一定的。事实上,如上设计,
ProcessKeyboardEventInJava也只允许在
init函数中被调用。在其返回后被调用,就不合法了。所以,如果你是这个设计师,之前的设计中,是不是这个假设也有些太乐观了呢?
回页首
分析
调用者本来以为这个函数能帮他的大忙,现在却疑惑了,到底发生了什么?做错了什么呢?原因其实很简单,上面三个全局变量中的
g_class已经是非法的了,生命周期在退出
init之后就结束了。
这里要澄清的是
g_mid,由于它不是一个
jobject,所以只要它对应的 class 没有被卸载,在退出
init之后仍可以使用,没问题;而
g_env对于同一个
thread 来说,它是唯一的,所以只要是
init和
ProcessKeyboardEventInJava处于一个
thread,初始化后它的值在这个 thread 没有中止之前,都一直是合法的。
那如何才能解决
g_class的非法引用带来的问题呢?
这首先涉及到 Java 和 Native 代码之间函数调用时,参数如何传递的问题。简单类型,也就是内置类型,比如 int, char 等是值传递(pass by value),而其它 Java 对象都是引用传递(pass by reference),这些对象引用由 JVM 传给 Native 代码,每个都有其生命周期。
其次,Java 对象的生命周期是由它的引用类型决定的,这里的引用分两种:local reference 和 global reference。Native 函数参数中 jobject 或者它的子类,其参数都是 local reference。Local reference 只在这个 Native 函数中有效,Native 函数返回后,引用的对象就被释放,它的生命周期就结束了。若要留着日后使用,则需根据这个 local reference 创建 global reference。Global reference
不会被系统自动释放,它仅当被程序明确调用 DeleteGlobalReference 时才被回收。
回页首
解决
于是解决的办法就出来了:在
init函数中,不是简单地把
jcalss参数保存,而是:
JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls) { …. g_class= (jclass)(env->NewGlobalRef(cls)); …. }
这样,无论
ProcessKeyboardEventInJava是在
init返回前还是返回后,调用它都是安全的,可行的了。
回页首
总结
若要在某个 Native 代码返回后,还希望能继续使用 JVM 提供的参数(比如 init 函数中的 jclass), 或者是过程中调用 JNI 函数的返回值(比如 g_mid),只要它是一个 jobject, 则需要为它创建一个 global reference,以后只能使用这个 global reference;若不是一个 jobject,则无需这么做。jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。JNIEnv 对于每个 thread 而言是唯一的,不能也不需要对它调用 NewGlobalReference。
相关文章推荐
- LeetCode *** 103. Binary Tree Zigzag Level Order Traversal
- 66、Integer.valueof(String)
- MATLAB区域标记提取指定编号区域
- Hive学习三
- 关于struts2中action请求会执行两次的问题
- 高斯金字塔
- 深入分析 Java 中的中文编码问题
- 数据库连接
- 结对编程——关于Fault、Error、Failure程序设计
- HelloChart--PieChartView(饼图)
- Linux无需开发底层驱动,从应用层获取GPIO中断
- HelloChart--ColumnChartView(柱形图)
- android开发游记:meterial design 使用circularReveal仿哔哩哔哩(bilibili)搜索框动画
- 体感设备
- Max 模型背面边面的不显示问题解决方案
- [初识总结]开源即时通讯IM Openfire+spark and (comet实现)
- java Runtime类
- 房坑
- Spring MVC 注解开发详解
- 去掉navbar或者tabbar的预留间距