跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

JNI 对象在函数调用中的生命周期

恽 益群 (yunyiqun@cn.ibm.com), 软件工程师, IBM
恽益群,IBM 中国软件开发中心软件工程师,专注于 Mac 平台上基于 Carbon C/C++ 和 Eclipse 的 Lotus Notes 开发。熟悉 Mac OS X,Carbon/Cocoa 开发接口以及此平台下的开发环境 XCode,Eclipse。

简介: Java 本地编程接口(Java Native Interface,JNI) 易懂且容易上手,但对于一些新手甚至于一些中级开发者,在编程中都没有把 JNI 对象的生命周期考虑在内。以至于经常碰见莫名的 Crash,或者 segmentation fault。在花费了相当精力去调试后,才发现是 JNI 对象使用不当,当初保存的 JNI 对象已经被系统回收,却仍试图去使用之。本文结合实际开发中遇到的此类问题,讲述和强调了 JNI 对象生命周期的概念,并提出正确的解决方法,希望能和读者分享自己的经验和教训。

发布日期: 2007 年 12 月 06 日
级别: 中级
访问情况 : 2956 次浏览
评论: 


问题

在 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 方法。那么调用所必需的参数 JNIEnvjclassjmethodID,是如何获得呢?从上面的代码中可以看出,它们来自保存的全局变量 g_envg_classg_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)
        ….
    }

}
			

但万一 ProcessKeyboardEventInJavaJava_SampleTest_init(以下简称 init)返回后的其他时机被调用了,会怎样呢?那时可就没有这么幸运了,初衷是肯定达不到了,而发生 crash 或者 segmentation fault 这类错误则几乎是一定的。事实上,如上设计,ProcessKeyboardEventInJava 也只允许在 init 函数中被调用。在其返回后被调用,就不合法了。所以,如果你是这个设计师,之前的设计中,是不是这个假设也有些太乐观了呢?


分析

调用者本来以为这个函数能帮他的大忙,现在却疑惑了,到底发生了什么?做错了什么呢?

原因其实很简单,上面三个全局变量中的 g_class 已经是非法的了,生命周期在退出 init 之后就结束了。

这里要澄清的是 g_mid,由于它不是一个 jobject,所以只要它对应的 class 没有被卸载,在退出 init 之后仍可以使用,没问题;而 g_env 对于同一个 thread 来说,它是唯一的,所以只要是 initProcessKeyboardEventInJava 处于一个 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。


参考资料

学习

讨论

关于作者

恽益群,IBM 中国软件开发中心软件工程师,专注于 Mac 平台上基于 Carbon C/C++ 和 Eclipse 的 Lotus Notes 开发。熟悉 Mac OS X,Carbon/Cocoa 开发接口以及此平台下的开发环境 XCode,Eclipse。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=271389
ArticleTitle=JNI 对象在函数调用中的生命周期
publish-date=12062007
author1-email=yunyiqun@cn.ibm.com
author1-email-cc=zoucl@cn.ibm.com

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。