图3.2展示了一个程序员如何在字符串相关的函数中做选择。
![](https://box.kancloud.cn/2016-02-24_56cd74cb3b632.png)
图3.2 在JNI字符串函数中做选择。
如果你想让你的代码发布在JDK1.1的目标平台上,除了Get/ReleaseStringChars和Get/ReleaseStringUTFChars你毫无选择。
如果你是在Java 2 SDK release 1.2及其以上版本上编程,你想讲字符串内容拷贝到事先分配好的内存中区,你可以使用GetStringRegion或者GetStringUTFRegion.
对于很小的固定大小的字符串,Get/SetStringRegion和Get/SetStringUTFRegion往往更优因为你可以在C堆栈上开辟空间,这样的代价相对来说小得多了,拷贝一个小字符串的代价是微乎其微的。
使用Get/SetStringRegion和Get/SetStringUTFRegion的一个好处是它们不必开辟内存空间,因此也就不会抛出out-of-memory相关的异常。如果你可以确定下标越界这种情况不会发生你就永远不需要进行检查。
使用Get/SetStringRegion和Get/SetStringUTFRegion的另一个好处是你可以指定要拷贝的字符串在原始字符串中的其实位置以及要拷贝的字符个数。这个函数适用于那些只需要访问一个比较长的字符串的部分子字符串的场景。
GetStringCritical必须被小心的使用,必须保证在持有GetStringCritical返回的指针的过程中,原生代码没有在虚拟机中开辟任何新的空间,而且不能执行任何阻塞式操作,否则有可能会引起死锁。
以下是使用GetStringCritical的错误示例,以下函数使用GetStringCritical获取了一个字符串,然后调用fprint函数将这个字符串输出到指定的文件描述符fd。
~~~
/* This is not safe! */
const char* c_str = (*env)->GetStringCritical(env, j_str, 0);
i(!c_str) {
/* error handling */
}
fprintf(fd, "%s\n", c_str);
(*env)->ReleaseStringCritical(env, j_str, c_str);
~~~
以上代码的问题在于当当前线程的垃圾回收被禁止的时候, 写入一个文件并不总是安全的。举个例子,假设另一个线程T正在等待从fd 对应的文件读取内容。让我们跟进一步假设文件系统的缓冲机制的实现是这样的:fprintf调用会一直等待线程T完成从fd读取所有数据。 我们创建了一个可能产生死锁的场景:如果线程T不能够开辟足够的空间来缓存从fd读取的内容,它就必须发起一次垃圾回收请求。但是垃圾回收请求又必须等到当前线程执行ReleaseStringCritical函数,而按照我们以上代码,这个操作在frpintf返回之前是不可能被调用的,这样的话fprintf函数就会一直等待。
以下代码,与上边的示例代码相似,但是他是不会产生任何死锁的:
/*This code segment is OK。*/
const char *c_str = (*env)->GetStringCritical(env, j_str, 0);
if(!c_str) {
/* error handling */
}
DrawString(c_str);
(*env)->ReleaseStringCritical(env, j_str, c_str);
DrawString是一个系统API调用, 它直接将字符串输出到屏幕。除非屏幕显示器的驱动也是跟当前代码在同一个Java虚拟机中运行的Java程序,DrawString函数是永远不会阻塞或者无限等待垃圾回收的。
总的来说,在使用Get/ReleaseStringCritical函数调用的时候,你㤇尽可能的多考虑你的代码是否会发生阻塞场景。
- 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函数使用规范