3. JNI数据类型映射与jstring转换
为什么需要 jstring 转换
Java 的 String 是 UTF-16 编码的引用类型,而 C 语言没有字符串类型,只有以 '\0' 结尾的 char*。JNI 把 Java 字符串抽象为 jstring,它只是一个不透明句柄,不能直接当 char* 使用。必须通过 JNI 函数把 jstring 转成 C 可读的字符缓冲区,处理完后再释放,否则会造成内存泄漏或 JVM 崩溃。
核心转换函数
| 函数 | 作用 | 关键点 |
|---|---|---|
GetStringUTFChars(env, jstr, &isCopy) | 把 jstring 转为修改版 UTF-8 的 const char* | 可能返回副本,必须配对释放 |
ReleaseStringUTFChars(env, jstr, chars) | 释放上一步获得的缓冲区 | 参数必须是同一个 jstring 和指针 |
GetStringUTFLength(env, jstr) | 获取 UTF-8 字节长度 | 不含结尾 '\0' |
NewStringUTF(env, chars) | 用 C 字符串创建新的 jstring 返回 | 输入需为合法修改版 UTF-8 |
注意:
GetStringUTFChars 返回的是“修改版 UTF-8”,与标准 UTF-8 在补充字符(如 emoji)上不同。若需标准 UTF-8,应改用 GetStringChars 获取 UTF-16 再自行编码。转换与生命周期
完整实现
下面实现一个本地方法 processName:接收 Java 字符串,转成 C 字符串后拼接后缀,再通过 NewStringUTF 返回新字符串。
// NativeLib.java
public class NativeLib {
static { System.loadLibrary("native-lib"); }
public native String processName(String name);
public static void main(String[] args) {
System.out.println(new NativeLib().processName("JNI"));
}
}
// native-lib.c
#include <jni.h>
#include <string.h>
#include <stdlib.h>
JNIEXPORT jstring JNICALL
Java_NativeLib_processName(JNIEnv *env, jobject thiz, jstring name) {
// 1. jstring -> C 字符串
const char *c_name = (*env)->GetStringUTFChars(env, name, NULL);
if (c_name == NULL) {
return NULL; // 内存不足,直接返回
}
// 2. 在 C 侧处理:拼接后缀
const char *suffix = "_processed";
size_t len = strlen(c_name) + strlen(suffix) + 1;
char *result = (char *)malloc(len);
if (result == NULL) {
(*env)->ReleaseStringUTFChars(env, name, c_name);
return NULL;
}
strcpy(result, c_name);
strcat(result, suffix);
// 3. 释放 GetStringUTFChars 获得的缓冲区
(*env)->ReleaseStringUTFChars(env, name, c_name);
// 4. C 字符串 -> 新 jstring 返回
jstring jresult = (*env)->NewStringUTF(env, result);
// 5. 释放自己 malloc 的内存
free(result);
return jresult;
}
编译与运行
# 生成头文件
javac -h . NativeLib.java
# 编译动态库(Linux)
gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
native-lib.c -o libnative-lib.so
# 运行
java -Djava.library.path=. NativeLib
预期输出:
JNI_processed
验证与排错
- 内存泄漏检查:若忘记调用
ReleaseStringUTFChars,在循环调用场景下 JVM 堆外内存会持续增长。可用valgrind --leak-check=full java ...观察。 - 空指针:
GetStringUTFChars在内存不足时返回NULL,必须先判空再使用。 - 编码问题:若 Java 字符串含 emoji,
GetStringUTFChars得到的是修改版 UTF-8(代理对编码为 6 字节),直接当标准 UTF-8 输出可能乱码。 - 释放顺序:先
ReleaseStringUTFChars再NewStringUTF是安全的;反过来若NewStringUTF失败返回NULL,仍需保证原缓冲区被释放。
记忆口诀:Get 必 Release,New 不用 Release。
GetStringUTFChars 借来的缓冲区要还,NewStringUTF 造出的 jstring 归 JVM 管。