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);
案例:使用局部引用
-
本地方法的声明
native Person getPerson(); -
本地方法的实现
#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; } -
使用本地方法返回的局部引用
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); -
测试结果
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创建的对象
- 定义三个本地方法
- "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);
}
- 本地方法实现实现
注意指针转换时需要使用
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);
}
- 测试在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 +
'}';
}
}
- 测试结果
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垃圾回收
-
定义本地方法
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); } -
实现本地方法
#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); } -
测试是否会被JVM进行GC
-
限制JVM的最大使用内存为10m
-Xmx10m -
打印GC日志
-XX:+PrintGC
while (true){ globalReferenceTest.getPersonHandler(); } -
-
测试结果,触发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}