Selaa lähdekoodia

# doc:学习JNI三种引用类型的文档

yangyi 2 viikkoa sitten
vanhempi
säilyke
58088845e4
1 muutettua tiedostoa jossa 546 lisäystä ja 0 poistoa
  1. 546 0
      doc/JNI中的三种引用类型.md

+ 546 - 0
doc/JNI中的三种引用类型.md

@@ -0,0 +1,546 @@
+# 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, "<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. 使用本地方法返回的局部引用
+
+   ```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, "<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);
+}
+
+```
+
+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, "<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 `
+
+   ```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)