为了让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个字节的检查)。