JNI中的三种引用类型.md 18 KB

JNI中的三种引用类型

特性 局部引用 (Local) 全局引用 (Global) 弱全局引用 (Weak Global)
生命周期 Native 方法执行期间 直到手动释放 直到手动释放 (或被GC回收对象)
跨线程使用
阻止 GC
创建函数 (大多数 JNI 函数自动创建) NewGlobalRef NewWeakGlobalRef
释放函数 自动 或 DeleteLocalRef DeleteGlobalRef DeleteWeakGlobalRef
主要用途 绝大多数 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);
      }
      
  2. 本地方法实现实现

注意指针转换时需要使用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 +
                '}';
    }
    }
    
  2. 测试结果

    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();
   }
  1. 测试结果,触发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}
    

参考资料

案例代码仓库