# JNI中的三种引用类型 | 特性 | 局部引用 (Local) | 全局引用 (Global) | 弱全局引用 (Weak Global) | | :------------- | :------------------------ | :---------------------------- | :----------------------------- | | **生命周期** | Native 方法执行期间 | 直到手动释放 | 直到手动释放 (或被GC回收对象) | | **跨线程使用** | 是 | 是 | 是 | | **阻止 GC** | 否 | 是 | 否 | | **创建函数** | (大多数 JNI 函数自动创建) | `NewGlobalRef` | `NewWeakGlobalRef` | | **释放函数** | 自动 或 `DeleteLocalRef` | `DeleteGlobalRef` | `DeleteWeakGlobalRef` | | **主要用途** | 绝大多数 JNI 操作 | 缓存类、对象等跨方法/线程使用 | 缓存不阻止 GC 的引用,如观察者 | > 网络上有些资料说局部引用无法跨线程使用 > 猜测指的是在C/C++中无法跨线程使用,但在JVM中可以跨线程使用,JNI返回的可能是拷贝后数据,不是原生的C/C++中的数据 ## 局部引用 通过在本地方法内部直接创建的引用都是局部引用,也可以使用`NewLocalRef`函数进行创建,只在本地方法内生效,本地方法调用结束,局部引用会被自动回收,也可以可以使用`DeleteLocalRef`函数进行主动释放 创建一个字符串的局部引用 ```c //字符串局部引用 jstring name = (*env)->NewStringUTF(env, "杨逸"); //使用NewLocalRef函数显示创建局部引用 jobject localRef = (*env)->NewLocalRef(env,name); //显示释放局部引用 (*env)->DeleteLocalRef(env,localRef); ``` ### 案例:使用局部引用 1. 本地方法的声明 ```java native Person getPerson(); ``` 2. 本地方法的实现 ```c #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, "", "(Ljava/lang/String;I)V"); //3.创建对象 jstring name = (*env)->NewStringUTF(env, "杨逸-局部引用"); jobject person = (*env)->NewObject(env, class_Person, constructor,name,18); return person; } ``` 3. 使用本地方法返回的局部引用 ```java 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. 测试结果 ```tex false person = Person{name='杨逸-局部引用', age=18} ``` ### 案例:测试局部引用能否跨线程使用 - 测试代码 ```java 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(); ``` - 测试结果,可以跨线程使用 ```tex 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创建的全局引用 ```java 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位系统中占用大小不同导致精度丢失 ```c #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, "", "(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); } ``` 3. 测试在JVM中使用JNI创建的对象 ```java 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 + '}'; } } ``` 4. 测试结果 ```tex person.getName() = 杨逸 person.getAge() = 18 Person{name='杨逸', age=18} ``` ### 案例:测试全局引用是否会被JVM垃圾回收 限制JVM的内存大小,在Java程序中一直使用JNI创建对象的全局引用,预期会出现OOM错误 > 指定JVM最大使用内存的参数`-Xmx10M` > > 打印JVM的GC详细日志参数`-XX:+PrintGCDetails` - Java测试代码 ```java while (true){ globalReferenceTest.getPersonHandler(); } ``` - 测试结果,触发OOM错误,JNI全局引用无法被JVM自动回收 ```tex [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" ``` ### 案例:测试全局引用能否跨线程使用 - 测试代码 ```java 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(); ``` - 测试结果,全局引用可以跨线程使用 ```tex Thread.currentThread().getName() = main Thread.currentThread().getName() = Thread-0 person = Person{name='杨逸', age=18} ``` ## 全局弱引用 全局弱引用需要使用`NewWeakGlobalRef`函数进行创建,也可以返回给JVM使用,但是在JVM内存不足时会被垃圾回收,也可以使用`DeleteWeakGlobalRef`函数进行主动释放 用法与全局引用类型,注意JVM内存不足时会被JVM进行GC ### 案例:测试全局弱引用是否会被JVM垃圾回收 1. 定义本地方法 ```java 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. 实现本地方法 ```c #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, "", "(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 ` ```java while (true){ globalReferenceTest.getPersonHandler(); } ``` 4. 测试结果,触发GC是JNI创建的全局弱引用会被垃圾回收 ```tex 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 ``` ### 案例:测试弱全局引用能否跨线程使用 - 测试代码 ```java 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(); ``` - 测试结果,弱全局引用可以跨线程使用 ```tex Thread.currentThread().getName() = main Thread.currentThread().getName() = Thread-0 person = Person{name='杨逸-弱引用', age=18} ``` ## 参考资料 [案例代码仓库](https://git.anyi.space/yangyi/JNI_learn)