为了让Java虚拟机有能力直接返回一个指向java.lang.String字符串的指针,Java 2SDK release 1.2推出了一组新的JNI函数。Get/ReleaseStringCritical.表面上看,他们看起来很像Get/ReleaseStringChars, 如果可能的话都会直接返回字符串的指针,否则返回这个字符串的拷贝。然后事实上,这些函数却是有各自不同的使用场景的。
你必须像对待临界区中的资源一样对待这对这对函数所处理的内容,在一个临界区中,原生代码不能调用任何JNI函数或者任何可能会阻塞当前线程的原生函数。例如,当前线程必须不能在一个被其他线程写入的I/O流上等待输入。
这些限制使得Java虚拟机在原生代码持有一个从GetStringCritical获取的字符串指针的时候使垃圾回收无效成为可能。当垃圾回收无效的时候,所有触发垃圾回收的线程都会被阻塞。在Get/ReleaseStringCritical之间的原生代码必须没有阻塞式调用或者在Java虚拟机中开辟内存。否则,Java虚拟机会发生思索,想象一下如下场景:
在当前线程结束并释放出垃圾回收权利之前一个由其他线程触发的垃圾回收操作是不能执行的。
同时,由于阻塞调用需要获取一个被另一个线程持有的锁,该线程也正在等待执行垃圾回收,当前线程不能继续执行。
重叠的多次使用GetStringCritical和ReleaseStringCritical函数是安全的,例如。
~~~cpp
jchar* s1 = (*env)->GetStringCritical(env, jstr1);
if(!s1) {
/* error handling */
}
jchar* s2 = (*env)->GetStringCritical(env, jstr2);
if(!s2) {
(*env)->ReleaseStringCritical(env, jstr1, s1);
/* error handling */
}
/* use s1 and s2 */
(*env)->ReleaseStringCritical(env, jstr1, s1);
(*env)->ReleaseStringCritical(env, jstr2, s2);
~~~
Get/ReleaseStringCritical这对函数不需要严格的按照栈顺序嵌套使用。为了处理内存溢出异常,我们必须要对GetStringCritical的返回值做判空检查,如果Java虚拟机内部以不同的形式存储数组,那么GetStringCritical就仍然需要开辟内存用于复制字符数组内容。例如,Java虚拟机有可能没有连续的存储数组,在这种情况下,GetStringCritical必须拷贝jstring中的所有字符来以保证返回给原生代码的字符串是一个连续的字符数组。
为了避免死锁,你必须保证原生代码在调用GetStringCritical之后以及调用ReleaseStringCritical之前没有调用任何JNI函数,除了重叠调用的Get/ReleaseStringCritical函数。
JNI并不支持GetStringUTFCritical和ReleaseStringUTFCritical函数,这样的函数将会总是需要虚拟机为字符串开辟空间并做必须的转换和赋值,因为大多数的虚拟机内部实现都是使用Unicode格式标识字符串的。
Java 2SDK release 1.2中另一组增加的函数是GetStringRegion和GetStringUTFRegion.这些函数将字符串内容拷贝到一个预先申请好的缓冲区中。Prompt.getLine函数也可以使用GetStringUTFRegion如下实现:
~~~cpp
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv * env, jobject obj, jstring prompt){
/* assume the prompt string and user input has less than 128 characters */
char outbuf[128], inbuf[128];
int len = (*env)->GetStringLength(env, prompt);
(*env)->GetStringUTFRegin(env, prompt, 0, len, outbuf);
printf("%s", output);
scanf("%s", inbuf);
return (*env)->NewStringUTF(env, inbuf);
}
~~~
GetStringUTFRegion函数需要一个开始索引和长度,都按Unicode字符的数量计算。这个函数也会进行边界检查,如果有需要,它会抛出StringIndexOutOfBoundsException. 在以上代码示例中, 我们从原始字符串获取它的长度,所以我们确定不会出现下标越界的状况。(当然了,我们只是通过注释假定字符串prompt的长度小于128,并没有从代码上检验)。
看起来代码比GetStringUTFChars要简单一点, 因为GetStringUTFRegion没有开辟内存,我们不需要检查out-of-memory这种状况。(同样,以上代码缺少对用户输入少于128个字节的检查)。
- JNI编程指南翻译
- 第一部分:介绍与入门
- 介绍
- Java平台与宿主环境
- JNI的角色
- 使用JNI的潜在风险
- 什么时候应该使用JNI
- JNI的进化
- 示例程序
- 入门
- 概览
- 声明一个Native方法
- 编译HelloWorld类
- 创建Native方法头文件
- 实现Native方法
- 编译C源码并创建一个Native库
- 执行程序
- 第二部分:编程指南
- 基础类型, Strings, Arrays
- 一个简单的Native方法
- Native方法实现的C语言原型
- Native方法的参数
- 类型映射
- 访问Strings
- 转换成为Native Strings
- 释放Native Strings资源
- 创建新的Strings
- String相关的其他JNI函数
- Java SDK2 中的新的String相关的JNI方法
- JNI String函数总结
- 在String函数中选择
- 访问Arrays
- 在C语言中操作数组
- 访问基础类型的数组
- JNI基础类型数组操作函数总结
- 如何在基础类型数组函数中做选择
- 访问对象数组
- 属性和方法
- 访问属性
- 如何访问一个对象实例的属性
- 属性描述符
- 访问静态属性
- 调用方法
- 调用对象实例方法
- 获取方法描述符
- 调用静态方法
- 调用对象的基类方法
- 调用构造函数
- 缓存属性和方法ID
- 在使用时缓存
- 在类初始化的时候缓存
- 两种ID缓存方式的对比
- JNI属性和方法操作的性能
- 局部和全局引用
- 局部和全局引用说明
- 局部引用
- 全局引用
- 弱全局引用
- 引用的比较
- 引用资源的释放
- 释放局部引用
- JDK2中的局部引用管理
- 释放全局引用
- 管理引用的原则
- 异常
- 概览
- 在Native代码中捕获与抛出异常
- 工具函数
- 异常处理最佳实践
- 异常检查
- 异常处理
- 工具函数中的异常
- The Invocation Interface
- 创建Java虚拟机
- 链接ava虚拟机到Native应用程序
- 链接某个确定的虚拟机实现
- 链接未知虚拟机
- 绑定Native线程
- 一些JNI特性
- JNI与线程
- 约束
- 监视进入与退出
- 监视等待和通知
- 如何从当前Context中获取JNIEnv的指针
- 匹配线程模型
- 编写国际化代码
- 注册Native方法
- 加载与卸载时机
- 加载时机
- 卸载时机
- 反射的支持
- 使用C++实现JNI
- 改变现有的Native库
- 一对一映射关系
- 共享桩
- 一对一映射VS共享桩
- 共享桩的实现
- Peer类
- Java平台中的Peer类
- 释放Native数据结构
- Peer类背后的指针
- 缺陷与陷阱
- 错误检查
- 向JNI函数传递非法参数
- jclass与jobject的困惑
- jboolean类型参数的截断机制
- Java应用与Native代码之间的边界
- ID与引用的困惑
- 缓存属性和方法ID
- 终结Unicode 字符串
- Violating访问控制原则
- 无视国际化
- 保持虚拟机资源
- 被滥用的局部引用创建
- 跨线程使用JNIEnv
- 错误使用的线程模型
- 第三部分:详细说明
- JNI设计概览
- 设计目标
- 加载Native库
- 类加载器
- 类加载器与Native库
- 加载Native库
- 类型安全限制
- 卸载Native库
- 链接Native方法
- 调用转换
- JNIEnv接口指针
- JNIEnv接口指针的组织
- 接口指针的好处
- 传递数据
- 全局和局部引用
- 局部引用的实现
- 弱全局引用
- 访问对象
- 访问原始数组
- 属性和方法
- 错误和异常
- 如果没有对编码错误的检查
- Java虚拟机异常
- 异步异常
- JNI类型
- 原始类型和引用类型
- 原始类型
- 引用类型
- jvalue类型
- 属性和方法ID
- 字符串格式
- UTF-8字符串
- 类描述符
- 属性描述符
- 方法描述符
- 常量
- JNI函数
- JNI函数总结
- 直接导出调用的接口函数
- Java虚拟机接口
- 在Native库中定义的函数
- JNIEnv接口
- JNI函数使用规范