3. JNI数据类型映射与jstring转换

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 再自行编码。

转换与生命周期

Java 侧 String name = "JNI" UTF-16 引用对象 jstring 句柄 不透明引用 不可直接解引用 C 字符缓冲区 const char* 修改版 UTF-8 参数传入 GetStringUTFChars ReleaseStringUTFChars 释放缓冲区 NewStringUTF C 字符串 → 新 jstring 处理后的 C 字符串 返回 jstring 关键约束:Get 与 Release 必须成对出现;NewStringUTF 创建的新 jstring 由 JVM 管理,无需手动释放

完整实现

下面实现一个本地方法 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 管。