JNI中的三种引用类型

特性局部引用 (Local)全局引用 (Global)弱全局引用 (Weak Global)
生命周期Native 方法执行期间直到手动释放直到手动释放 (或被GC回收对象)
跨线程使用
阻止 GC
创建函数(大多数 JNI 函数自动创建)NewGlobalRefNewWeakGlobalRef
释放函数自动 或 DeleteLocalRefDeleteGlobalRefDeleteWeakGlobalRef
主要用途绝大多数 JNI 操作缓存类、对象等跨方法/线程使用缓存不阻止 GC 的引用,如观察者

网络上有些资料说局部引用无法跨线程使用
猜测指的是在C/C++中无法跨线程使用,但在JVM中可以跨线程使用,JNI返回的可能是拷贝后数据,不是原生的C/C++中的数据

局部引用

通过在本地方法内部直接创建的引用都是局部引用,也可以使用NewLocalRef函数进行创建,只在本地方法内生效,本地方法调用结束,局部引用会被自动回收,也可以可以使用DeleteLocalRef函数进行主动释放

创建一个字符串的局部引用

//字符串局部引用
jstring name = (*env)->NewStringUTF(env, "杨逸");
//使用NewLocalRef函数显示创建局部引用
jobject localRef = (*env)->NewLocalRef(env,name);
//显示释放局部引用
(*env)->DeleteLocalRef(env,localRef);

案例:使用局部引用

  1. 本地方法的声明

    native Person getPerson();
    
  2. 本地方法的实现

    #include "space_anyi_jni_reference_LocalReferenceTest.h"
    JNIEXPORT jobject JNICALL Java_space_anyi_jni_reference_LocalReferenceTest_getPerson(JNIEnv *env, jobject this){
        //1.获取对应的jclass
        jclass class_Person = (*env)->FindClass(env, "Lspace/anyi/jni/reference/Person;");
        //2.获取对应的构造方法
        jmethodID constructor = (*env)->GetMethodID(env, class_Person, "<init>", "(Ljava/lang/String;I)V");
        //3.创建对象
        jstring name = (*env)->NewStringUTF(env, "杨逸-局部引用");
        jobject person = (*env)->NewObject(env, class_Person, constructor,name,18);
        return person;
    }
    
  3. 使用本地方法返回的局部引用

    Person person = localReferenceTest.getPerson();
    System.gc();
    try {
        Thread.sleep(1000*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Objects.isNull(person));
    System.out.println("person = " + person);
    
  4. 测试结果

    false
    person = Person{name='杨逸-局部引用', age=18}
    

案例:测试局部引用能否跨线程使用

  • 测试代码
Person person = localReferenceTest.getPerson();
System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
new Thread(()->{
    //全局引用跨线程使用
    System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    System.out.println("person = " + person);
}).start();
  • 测试结果,可以跨线程使用
Thread.currentThread().getName() = main
Thread.currentThread().getName() = Thread-0
person = Person{name='杨逸-局部引用', age=18}

全局引用

全局引用需要使用NewGlobalRef函数进行创建,全局引用可以返回给JVM使用,全局引用不会被JVM进行垃圾回收,需要手动调用DeleteGlobalRef函数进行释放(有内存泄漏的风险)

案例:在JVM中使用JNI创建的对象

  1. 定义三个本地方法
    • "getPersonHandler":在JNI中创建一个全局引用,并返回句柄
    • "getPerson":根据句柄获取全局引用对象
    • "freePerson":释放JNI创建的全局引用
class GlobalReferenceTest{
    /**
     * 在JNI中创建一个全局引用,并返回句柄
     * @return long 全局引用的句柄
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:29:12
     * @since 1.0.0
     */
    native long getPersonHandler();

    /**
     * 根据句柄获取全局引用对象
     * @param handler 句柄
     * @return {@code Person } 全局引用对象
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:30:09
     * @since 1.0.0
     */
    native Person getPerson(long handler);

    /**
     * 释放JNI创建的全局引用
     * @param handler 句柄
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:30:38
     * @since 1.0.0
     */
    native void freePerson(long handler);
}
  1. 本地方法实现实现

注意指针转换时需要使用intptr_t类型作为中级类型,避免因为指针在32位系统和63位系统中占用大小不同导致精度丢失

#include "space_anyi_jni_reference_GlobalReferenceTest.h"
JNIEXPORT jlong JNICALL Java_space_anyi_jni_reference_GlobalReferenceTest_getPersonHandler(JNIEnv *env, jobject this){
    //1.获取对应的jclass
    jclass class_Person = (*env)->FindClass(env, "Lspace/anyi/jni/reference/Person;");
    //2.获取对应的构造方法
    jmethodID constructor = (*env)->GetMethodID(env, class_Person, "<init>", "(Ljava/lang/String;I)V");
    //3.创建对象
    jstring name = (*env)->NewStringUTF(env, "杨逸");
    jobject person = (*env)->NewObject(env, class_Person, constructor,name,18);
    //4.获取全局引用
    jobject globalReference = (*env)->NewGlobalRef(env, person);
    //5.转换为句柄,使用intptr_t类型作为中间类型,避免精度丢失
    jlong handler = (jlong)(intptr_t)globalReference;
    return handler;
}

JNIEXPORT jobject JNICALL Java_space_anyi_jni_reference_GlobalReferenceTest_getPerson(JNIEnv *env, jobject this, jlong handler){
    //1.通过句柄获取全局引用,使用intptr_t类型作为中间类型
    jobject person = (jobject)(intptr_t)handler;
    //2.返回对象
    return person;
}

JNIEXPORT void JNICALL Java_space_anyi_jni_reference_GlobalReferenceTest_freePerson(JNIEnv *env, jobject this, jlong handler){
    //1.通过句柄释放全局引用的内存
    (*env)->DeleteGlobalRef(env, (jobject)(intptr_t)handler);
}

  1. 测试在JVM中使用JNI创建的对象
package space.anyi.jni.reference;

/**
 * @ProjectName: JNI_learn
 * @FileName: Main
 * @Author: 杨逸
 * @Data:2026/3/4 21:25
 * @Description:
 */
public class Main {
    static {
        System.load("E:\\tmp\\JNI_learn\\c\\reference\\JNI_globalReference.dll");
    }
    public static void main(String[] args) {
        GlobalReferenceTest.test();
    }
}
class GlobalReferenceTest{
    static GlobalReferenceTest globalReferenceTest = new GlobalReferenceTest();

    /**
     * 测试在JVM中使用JNI创建的对象全局引用
     * @description: 
     * @author: 杨逸
     * @data:2026/03/05 21:06:10
     * @since 1.0.0
     */
    static void test(){
        //获取全局引用的句柄
        long personHandler = globalReferenceTest.getPersonHandler();

        //根据句柄获取全局引用对象
        Person person = globalReferenceTest.getPerson(personHandler);
        System.out.println("person.getName() = " + person.getName());
        System.out.println("person.getAge() = " + person.getAge());
        System.out.println(person);
        //释放全局引用
        globalReferenceTest.freePerson(personHandler);
    }

    /**
     * 测试JNI创建的对象全局引用是否会被JVM垃圾回收
     * @description: 
     * @author: 杨逸
     * @data:2026/03/05 21:06:34
     * @since 1.0.0
     */
    static void testJVMGC(){
        while (true){
            globalReferenceTest.getPersonHandler();
        }
    }
    /**
     * 在JNI中创建一个全局引用,并返回句柄
     * @return long 全局引用的句柄
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:29:12
     * @since 1.0.0
     */
    native long getPersonHandler();

    /**
     * 根据句柄获取全局引用对象
     * @param handler 句柄
     * @return {@code Person } 全局引用对象
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:30:09
     * @since 1.0.0
     */
    native Person getPerson(long handler);

    /**
     * 释放JNI创建的全局引用
     * @param handler 句柄
     * @description:
     * @author: 杨逸
     * @data:2026/03/04 21:30:38
     * @since 1.0.0
     */
    native void freePerson(long handler);
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 测试结果
person.getName() = 杨逸
person.getAge() = 18
Person{name='杨逸', age=18}

案例:测试全局引用是否会被JVM垃圾回收

限制JVM的内存大小,在Java程序中一直使用JNI创建对象的全局引用,预期会出现OOM错误

指定JVM最大使用内存的参数-Xmx10M

打印JVM的GC详细日志参数-XX:+PrintGCDetails

  • Java测试代码
while (true){
    globalReferenceTest.getPersonHandler();
}
  • 测试结果,触发OOM错误,JNI全局引用无法被JVM自动回收
[0.554s][info   ][gc             ] GC(32) Pause Full (G1 Compaction Pause) 9M->9M(10M) 16.621ms
[0.554s][info   ][gc,cpu         ] GC(32) User=0.02s Sys=0.00s Real=0.02s
[0.554s][info   ][gc,ergo        ] Attempting maximum full compaction clearing soft references
[0.554s][info   ][gc,start       ] GC(33) Pause Full (G1 Compaction Pause)
[0.554s][info   ][gc,task        ] GC(33) Using 1 workers of 10 for full compaction
[0.554s][info   ][gc,phases,start] GC(33) Phase 1: Mark live objects

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

案例:测试全局引用能否跨线程使用

  • 测试代码

    long personHandler = globalReferenceTest.getPersonHandler();
    Person person = globalReferenceTest.getPerson(personHandler);
    System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    new Thread(()->{
        //全局引用跨线程使用
        System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
        System.out.println("person = " + person);
    }).start();
    
  • 测试结果,全局引用可以跨线程使用

    Thread.currentThread().getName() = main
    Thread.currentThread().getName() = Thread-0
    person = Person{name='杨逸', age=18}
    

全局弱引用

全局弱引用需要使用NewWeakGlobalRef函数进行创建,也可以返回给JVM使用,但是在JVM内存不足时会被垃圾回收,也可以使用DeleteWeakGlobalRef函数进行主动释放

用法与全局引用类型,注意JVM内存不足时会被JVM进行GC

案例:测试全局弱引用是否会被JVM垃圾回收

  1. 定义本地方法

    class WeakGlobalReferenceTest {
        static WeakGlobalReferenceTest globalWeakReferenceTest = new WeakGlobalReferenceTest();
    
        /**
         * 测试在JVM中使用JNI创建的对象全局弱引用
         * @description:
         * @author: 杨逸
         * @data:2026/03/05 21:06:10
         * @since 1.0.0
         */
        static void test(){
            //获取全局弱引用的句柄
            long personHandler = globalWeakReferenceTest.getPersonHandler();
    
            //根据句柄获取全局引用对象
            Person person = globalWeakReferenceTest.getPerson(personHandler);
            System.out.println("person.getName() = " + person.getName());
            System.out.println("person.getAge() = " + person.getAge());
            System.out.println(person);
            //释放全局弱引用
            globalWeakReferenceTest.freePerson(personHandler);
        }
    
        /**
         * 测试JNI创建的对象全局引用是否会被JVM垃圾回收
         * @description:
         * @author: 杨逸
         * @data:2026/03/05 21:06:34
         * @since 1.0.0
         */
        static void testJVMGC(){
            while (true){
                globalWeakReferenceTest.getPersonHandler();
            }
        }
        /**
         * 在JNI中创建一个全局引用,并返回句柄
         * @return long 全局弱引用的句柄
         * @description:
         * @author: 杨逸
         * @data:2026/03/04 21:29:12
         * @since 1.0.0
         */
        native long getPersonHandler();
    
        /**
         * 根据句柄获取全局弱引用对象
         * @param handler 句柄
         * @return {@code Person } 全局引用弱对象
         * @description:
         * @author: 杨逸
         * @data:2026/03/04 21:30:09
         * @since 1.0.0
         */
        native Person getPerson(long handler);
    
        /**
         * 释放JNI创建的全局弱引用
         * @param handler 句柄
         * @description:
         * @author: 杨逸
         * @data:2026/03/04 21:30:38
         * @since 1.0.0
         */
        native void freePerson(long handler);
    }
    
  2. 实现本地方法

    #include "space_anyi_jni_reference_WeakGlobalReferenceTest.h"
    JNIEXPORT jlong JNICALL Java_space_anyi_jni_reference_WeakGlobalReferenceTest_getPersonHandler(JNIEnv *env, jobject this){
            //1.获取对应的jclass
            jclass class_Person = (*env)->FindClass(env, "Lspace/anyi/jni/reference/Person;");
            //2.获取对应的构造方法
            jmethodID constructor = (*env)->GetMethodID(env, class_Person, "<init>", "(Ljava/lang/String;I)V");
            //3.创建对象
            jstring name = (*env)->NewStringUTF(env, "杨逸-弱引用");
            jobject person = (*env)->NewObject(env, class_Person, constructor,name,18);
            //4.获取全局弱引用
            jobject weakGlobalReference = (*env)->NewWeakGlobalRef(env, person);
            //5.转换为句柄,使用intptr_t类型作为中间类型,避免精度丢失
            jlong handler = (jlong)(intptr_t)weakGlobalReference;
            return handler;
    }
    
    JNIEXPORT jobject JNICALL Java_space_anyi_jni_reference_WeakGlobalReferenceTest_getPerson(JNIEnv *env, jobject this, jlong handler){
        //1.通过句柄获取全局弱引用,使用intptr_t类型作为中间类型
        jobject person = (jobject)(intptr_t)handler;
        return person;
    }
    
    JNIEXPORT void JNICALL Java_space_anyi_jni_reference_WeakGlobalReferenceTest_freePerson(JNIEnv *env, jobject this, jlong handler){
        //1.通过句柄获取全局弱引用,使用intptr_t类型作为中间类型
        jobject weakGlobalReference = (jobject)(intptr_t)handler;
        //2.释放全局弱引用
        (*env)->DeleteWeakGlobalRef(env, weakGlobalReference);
    }
    
  3. 测试是否会被JVM进行GC

    • 限制JVM的最大使用内存为10m-Xmx10m

    • 打印GC日志-XX:+PrintGC

    while (true){
    	globalReferenceTest.getPersonHandler();
    }
    
  4. 测试结果,触发GC是JNI创建的全局弱引用会被垃圾回收

    C:\Users\杨逸\.jdks\corretto-17.0.17\bin\java.exe -Xmx10M -XX:+PrintGC "-javaagent:D:\english lujing\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar=26706:D:\english lujing\IntelliJ IDEA 2021.3.3\bin" -Dfile.encoding=UTF-8 -classpath E:\tmp\JNI_learn\target\classes space.anyi.jni.reference.Main
    [0.003s][warning][gc] -XX:+PrintGC is deprecated. Will use -Xlog:gc instead.
    [0.012s][info   ][gc] Using G1
    [0.122s][info   ][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 4M->1M(10M) 1.864ms
    [0.164s][info   ][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 4M->0M(10M) 1.348ms
    ...
    [21.186s][info   ][gc] GC(290) Pause Young (Normal) (G1 Evacuation Pause) 6M->1M(10M) 12.976ms
    [21.261s][info   ][gc] GC(291) Pause Young (Normal) (G1 Evacuation Pause) 6M->1M(10M) 13.076ms
    [21.337s][info   ][gc] GC(292) Pause Young (Normal) (G1 Evacuation Pause) 6M->1M(10M) 12.756ms
    
    Process finished with exit code 130
    

案例:测试弱全局引用能否跨线程使用

  • 测试代码

    long personHandler = weakGlobalReferenceTest.getPersonHandler();
    Person person = weakGlobalReferenceTest.getPerson(personHandler);
    System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    new Thread(()->{
        //全局引用跨线程使用
        System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
        System.out.println("person = " + person);
    }).start();
    
  • 测试结果,弱全局引用可以跨线程使用

    Thread.currentThread().getName() = main
    Thread.currentThread().getName() = Thread-0
    person = Person{name='杨逸-弱引用', age=18}
    

参考资料

案例代码仓库