1、8種基本數據類型
數據類型 | 分類 | 字節數 | 內存位數 | 是否最高位為符號位 (0正數1負數) | 取值范圍(數值形式) | 取值說明 |
---|---|---|---|---|---|---|
byte | 整數類型 | 1 | 8 | 是 | -128 ~ 127 | -2^7 ~ 2^7 - 1 (冪形式) |
short | 整數類型 | 2 | 16 | 是 | -32,768 ~ 32,767 | -2^15 ~ 2^15 - 1 (冪形式) |
int | 整數類型 | 4 | 32 | 是 | -2,147,483,648 ~ 2,147,483,647 | -2^31 ~ 2^31 - 1 (冪形式) |
long | 整數類型 | 8 | 64 | 是 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | -2^63 ~ 2^63 -1 (冪形式) |
float | 浮點類型(單精度) | 4 | 32 | 是 | 正數:1.4e-45 ~ 3.402e38 負數:-3.4028235e38 ~ -1.4e-45 | 6~7位有效十進制小數位 |
double | 浮點類型(雙精度) | 8 | 64 | 是 | 正數:4.9e-324 ~ 1.797e308 負數:-1.797e308 ~ 4.9e-324 | 15位十進制小數位 |
char | Unicode字符類型 | 2 | 16 | 否 | 0 ~ 65,535 | 0 ~ 2^16 -1 與 Unicode 編碼的直接對應,如下 char c = 65; // 輸出的不是65而是A字符 System.out.println?; |
boolean | 布爾類型 | 無固定 | 無固定 | 否 | 無固定 | 無固定,實際占用取決于 JVM 實現和使用場景 |
根據JVM的內存模型,基本數據類型存儲位置取決于聲明位置和使用方式:
- 方法內部聲明,則存儲在棧里
- 在實例類中聲明,則存儲在堆里
2、引用數據類型
1、自定義類或java api提供的類
存儲對象的引用(內存地址),指向實際堆存儲的位置。
Date now = new Date(); // 日期類
Person p = new Person(); // 自定義類實例
class Person {String name;int age;
}
字符串String 特別說明:
String有緩存機制,主要通過 字符串常量池(String Pool) 實現,相同內容的字符串只存儲一份,后續重復使用直接引用池中的對象。
字面量賦值(自動入池)
String s1 = "Fly"; // 第一次創建,存入常量池
String s2 = "Fly"; // 直接復用常量池中的對象
System.out.println(s1 == s2); // true(地址相同)
顯式調用 intern()(手動入池)
String s3 = new String("Fly"); // 在堆中創建新對象
String s4 = s3.intern(); // 將s3內容加入常量池(若池中已有則返回引用)
System.out.println(s1 == s4); // true(s4指向常量池對象)
new String() 不觸發自動緩存
String s5 = new String("Fly"); // 強制在堆中創建新對象
String s6 = new String("Fly"); // 另一個新對象
System.out.println(s5 == s6); // false(地址不同)
System.out.println(s1 == s5); // false(常量池 vs 堆新對象)
注意:運行時拼接(不觸發緩存)
// 示例1:編譯期優化(字面量拼接)
String a = "Fly" + "Fish"; // 編譯后自動合并為 "FlyFish",復用常量池對象// 示例2:運行時拼接(不觸發緩存)// 循環內拼接字符串用 StringBuilder,避免生成大量中間對象
String b = "Fly";
String c = b + "Fish"; // 運行時在堆中生成新對象
String d = "FlyFish";
System.out.println(a == d); // true(a、d在常量池)
System.out.println(c == d); // false(c在堆,d在常量池)
使用緩存減少對象創建開銷,加速字符串比較(==
比 equals()
快),緩存機制下 ==
有時有效,但非字面量字符串比較必須用 equals()
。
在java8之前內部使用 char[](字符數組)存儲數據,java9及之后為節省內存,改為 byte[] + 編碼標志(Latin-1 或 UTF-16),但邏輯上仍等價于字符序列。
2、接口類
存儲對象的引用(內存地址),指向實際堆存儲的位置。
interface Drawable {void draw();
}class Circle implements Drawable {public void draw() {System.out.println("Drawing circle");}
}Drawable d = new Circle(); // 接口引用指向實現類
3、數組類型
數組如果是引用對象存儲對象的引用(內存地址),指向實際堆存儲的位置。
數組如果是基本數據類型,則按基本數據類型存儲在一致。
int[] numbers = {1, 2, 3}; // 基本類型數組
String[] names = new String[5]; // 引用類型數組
int[][] matrix = {{1,2}, {3,4}}; // 多維數組
4、枚舉類型
引用對象存儲對象的引用(內存地址),指向實際堆存儲的位置。
enum Color {RED, GREEN, BLUE
}
Color c = Color.RED; // 枚舉引用
5、注解類型
引用對象存儲對象的引用(內存地址),指向實際堆存儲的位置。
@Retention(RetentionPolicy.RUNTIME)
@interface Author {String name();int version() default 1;
}@Author(name = "John")
class MyClass {...}
6、集合框架
List<String> list = new ArrayList<>(); // 有序列表
Set<Integer> set = new HashSet<>(); // 唯一值集合
Map<String, Integer> map = new HashMap<>(); // 鍵值對映射
對象存儲對象的引用(內存地址),指向實際堆存儲的位置。
7、8中基本數據類型對應的包裝類
基本類型 | 包裝類 | 自動裝箱/拆箱 | 值緩存 | 比較陷阱(建議始終用equals比較包裝對象) |
---|---|---|---|---|
byte | Byte | 類似 | -128 ~ 127 | Integer x = 100, y = 100; System.out.println(x == y); // true(緩存內) Integer m = 200, n = 200; System.out.println(m == n); // false(緩存外) |
short | Short | 類似 | -128 ~ 127 | 類似Integer |
int | Integer | Integer num = 42; // 自動裝箱 (int → Integer) int value = num; // 自動拆箱 (Integer → int) | -128 ~ 127 | 類似Integer |
long | Long | 類似 | -128 ~ 127 | 類似Integer |
float | Float | 自動裝箱都創建新對象。 | 無緩存 | 無 |
double | Double | 自動裝箱都創建新對象。 | 無緩存 | 無 |
char | Character | 類似 | ** 0 ~ 127**(ASCII字符范圍) | 類似Integer |
boolean | Boolean | 僅有兩個靜態實例Boolean.TRUE 和Boolean.FALSE ,所有自動裝箱均復用它們。 | 無 | 無 |
3、四大引用類型
GC回收時機
一、強引用
說明
強引用是 Java 中最常見、最默認的引用類型。只要一個對象被至少一個強引用指向,垃圾收集器 (GC) 就絕對不會回收它。只有當所有指向該對象的強引用都斷開(被設置為 null
或超出作用域)后,該對象才會變得可回收。
使用場景舉例
public class StrongReferenceExample {public static void main(String[] args) {// 1. 創建一個新的 Person 對象 "Alice"// alice 是一個強引用,指向堆內存中創建的 Person("Alice") 對象Person alice = new Person("Alice"); // 強引用建立!System.out.println("Alice created: " + alice.getName()); // 輸出: Alice created: Alice// 2. 讓 alice 引用指向一個 *新* 的 Person 對象 "Bob"// 現在,第一個 Person("Alice") 對象失去了它唯一的強引用 (alice 不再指向它)alice = new Person("Bob"); // 強引用指向了新對象,"Alice" 的強引用斷開!System.out.println("Now Alice refers to: " + alice.getName()); // 輸出: Now Alice refers to: Bob// 3. 顯式觸發垃圾收集 (注意:這只是建議,GC 不保證立即執行)for (int i = 0; i < 3; i++) {System.gc();}// 4. 給 GC 一點時間運行 (實際應用中通常不需要這樣),GC回收后日志即可看到:!!! GC is collecting Person: Alice !!!try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Program finished.");}}class Person {private String name;public Person(String name) {this.name = name;System.out.println("Person constructor: " + name + " created in memory.");}public String getName() {return name;}// 重點:覆寫 finalize 方法 (不推薦用于生產,僅用于演示GC行為)@Overrideprotected void finalize() throws Throwable {System.out.println("!!! GC is collecting Person: " + name + " !!!");super.finalize();}}
二、軟引用
說明
通過SoftReference
類創建,只有當內存不足時,才會被回收,所以比弱引用的生命周期更長點,適合實現內存敏感的緩存,如圖片緩存等。這些緩存對象在內存充足時可以提升性能,但在內存緊張時會被自動回收,為更重要的對象騰出空間,避免 OOM
JVM 不會一次性回收所有軟引用對象。回收過程是有策略、漸進式的,具體行為取決于 JVM 的實現和垃圾回收算法。
- 按需回收:只回收足夠多的軟引用對象來緩解當前內存壓力,而非一次性全部回收。
- 策略性回收:JVM 可能根據內部策略(如最近最少使用 LRU)選擇性地回收部分軟引用對象。如HotSpot 虛擬機使用類似 LRU(Least Recently Used)的策略,在每個軟引用對象關聯一個“時間戳”,記錄最近一次被訪問(通過
get()
方法)的時間。內存不足時,優先回收最久未被使用的軟引用對象。回收會持續進行,直到釋放的內存滿足需求或沒有更多可回收的軟引用。不同 JVM 版本或供應商(如 OpenJDK、Oracle JDK)的實現策略可能不同。 - JVM的垃圾回收算法:軟引用對象可能分布在新生代(Young Gen)或老年代(Old Gen)。觸發 Full GC(回收整個堆)時,才會全面掃描并回收老年代中的軟引用。如果 Minor GC(回收新生代)后內存仍然不足,可能直接觸發 Full GC 來回收老年代的軟引用。
使用場景舉例
適合實現內存敏感的緩存,如圖片緩存等。這些緩存對象在內存充足時可以提升性能,但在內存緊張時會被自動回收,為更重要的對象騰出空間,避免 OOM
/*** 軟引用例子*/
public class SoftReferenceExample {// 核心緩存結構:文件路徑 -> 文件內容的軟引用private final Map<String, SoftReference<String>> cache = new HashMap<>();// 引用隊列,用于跟蹤哪些軟引用已被GC回收(內容已被清除)private final ReferenceQueue<String> refQueue = new ReferenceQueue<>();// 從緩存獲取文件內容(如果存在且未被回收),否則從磁盤讀取并緩存public String getFileContent(String filePath) {// 1. 清理已被GC回收的緩存條目evictCollectedEntries();// 2. 嘗試從緩存中獲取軟引用SoftReference<String> softRef = cache.get(filePath);// 3. 如果軟引用存在,嘗試獲取其引用的實際內容String content = null;if (softRef != null) {content = softRef.get(); // get() 方法獲取被引用的對象,如果已被GC則返回null}// 4. 如果從軟引用成功獲取到內容(content != null),直接返回緩存內容if (content != null) {System.out.println("Retrieved from cache: " + filePath);return content;}// 5. 緩存未命中(軟引用不存在,或軟引用存在但其內容已被GC回收)System.out.println("Cache miss (or collected). Reading from disk: " + filePath);// 模擬從磁盤讀取文件內容 (實際應用中替換為真實IO操作)content = readFileFromDisk(filePath);// 6. 將新讀取的內容用軟引用包裝,并與文件路徑關聯放入緩存// 同時注冊引用隊列,以便后續知道該引用何時被回收softRef = new SoftReference<>(content, refQueue);cache.put(filePath, softRef);return content;}// 清理那些已經被垃圾回收器回收了內容的軟引用條目private void evictCollectedEntries() {SoftReference<? extends String> clearedRef;// 從引用隊列中取出所有已被GC回收的軟引用while ((clearedRef = (SoftReference<? extends String>) refQueue.poll()) != null) {// 遍歷緩存,找到這個被回收的軟引用對應的條目并移除// (注意:這里簡單遍歷,實際高效實現可能需要反向映射或其他結構)SoftReference<? extends String> finalClearedRef = clearedRef;cache.entrySet().removeIf(entry -> entry.getValue() == finalClearedRef);System.out.println("Evicted collected reference from cache.");}}// 模擬從磁盤讀取文件(簡單返回一個模擬的大字符串)private String readFileFromDisk(String filePath) {// 模擬讀取大文件:創建一個較大的字符串StringBuilder sb = new StringBuilder();sb.append("Content of file: ").append(filePath).append("\n");for (int i = 0; i < 10000; i++) { // 增加字符串大小以模擬大文件sb.append("This is line ").append(i).append(" in the file.\n");}return sb.toString();}// 測試主方法public static void main(String[] args) {SoftReferenceExample fileCache = new SoftReferenceExample();// 第一次讀取文件A - 會從磁盤讀取并緩存String contentA1 = fileCache.getFileContent("fileA.txt");System.out.println("Content A length: " + contentA1.length());// 第二次讀取文件A - 應該從緩存命中String contentA2 = fileCache.getFileContent("fileA.txt");System.out.println("Content A length (again): " + contentA2.length());// 模擬內存壓力:嘗試緩存多個大文件System.out.println("\nSimulating memory pressure by caching many files...");for (int i = 0; i < 100; i++) {fileCache.getFileContent("large_file_" + i + ".dat");}System.out.println("Cached many large files.");// 嘗試再次讀取文件A - 可能命中,也可能被回收了需要重新讀取System.out.println("\nTrying to access fileA.txt again...");String contentA3 = fileCache.getFileContent("fileA.txt");System.out.println("Content A length (after pressure): " + (contentA3 != null ? contentA3.length() : "null"));}
}
三、弱引用
說明
通過WeakReference
類創建的,下次GC時無論內存是否充足,只要對象僅被弱引用指向(沒有強引用或軟引用),無論內存是否充足,該對象都會被回收。
使用場景舉例
ThreadLocal內部實現的key即ThreadLocal本身就是用的弱引用,具體查看文章 java線程變量ThreadLocal用法篇
通常用在臨時緩存的場景使用,WeakHashMap舉例如下
static class Config {private final String value;private final byte[] payload; // 增加內存占用public Config(String value) {this.value = value;this.payload = new byte[12048]; // 每個配置對象占用2KB}}/*** 實例本身不可回收,生命周期與類加載器綁定,當鍵失去所有外部強引用時,GC時即可回收*/private static final WeakHashMap<Object, Config> cache = new WeakHashMap<>();private static final WeakHashMap<Object, Config> cache2 = new WeakHashMap<>();// 內存監控工具private static final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();// 打印內存使用情況private static void printMemoryStats(String label) {// 獲取堆內存使用情況MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();long usedHeap = heapUsage.getUsed() / 1024; // KB// 獲取非堆內存使用情況MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();long usedNonHeap = nonHeapUsage.getUsed() / 1024; // KBSystem.out.printf("[%s] 堆內存: %d KB | 非堆內存: %d KB | cache大小: %d | cache2大小: %d%n",label, usedHeap, usedNonHeap, cache.size(), cache2.size());}/*** 這種方式字符串在常量池中,所以key存在強引用,所以key不會被回收*/static void testStringLiteral() {System.out.println("\n===== 開始測試字符串字面量 =====");printMemoryStats("測試前");String key1 = "key1";String key2 = "key2";String key3 = "key3";String key4 = "key4";String key5 = "key5";String key6 = "key6";String key7 = "key7";String key8 = "key8";String key9 = "key9";String key10 = "key10";cache.putIfAbsent(key1, new Config("value_key1")); // 1KBcache.putIfAbsent(key2, new Config("value_key2")); // 1KBprintMemoryStats("添加中 " + 2);cache.putIfAbsent(key3, new Config("value_key3")); // 1KBcache.putIfAbsent(key4, new Config("value_key4")); // 1KBprintMemoryStats("添加中 " + 4);cache.putIfAbsent(key5, new Config("value_key5")); // 1KBcache.putIfAbsent(key6, new Config("value_key6")); // 1KBprintMemoryStats("添加中 " + 6);cache.putIfAbsent(key7, new Config("value_key7")); // 1KBcache.putIfAbsent(key8, new Config("value_key8")); // 1KBprintMemoryStats("添加中 " + 8);cache.putIfAbsent(key9, new Config("value_key9")); // 1KBcache.putIfAbsent(key10, new Config("value_key10")); // 1KBprintMemoryStats("添加中 " + 10);printMemoryStats("添加后");System.out.println("字面量測試完成");}/*** 使用 new String()在堆中創建的新對象,不存在強引用所以key可以回收,value值對象將失去來自 Map 的強引用* 沒有引用的value對象即可被GC回收。* 正常業務使用具體對象*/static void testNewString() {System.out.println("\n===== 開始測試 new String() =====");printMemoryStats("測試前");for (int i = 0; i < 10; i++) {// 使用 new String() - 可回收cache2.putIfAbsent(new String("key" + i), new Config("value"+i)); // 1KB// 每1000次打印一次內存if (i % 2 == 0) {printMemoryStats("添加中 " + i);}}printMemoryStats("添加后");System.out.println("new String() 測試完成");}/*** 執行GC并等待清理完成*/private static void forceGCAndWait() {System.out.println("\n觸發GC...");printMemoryStats("GC前");// 多次觸發GC確保執行for (int i = 0; i < 3; i++) {System.gc();try {Thread.sleep(200); // 給GC時間執行} catch (InterruptedException e) {Thread.currentThread().interrupt();}}printMemoryStats("GC后");}/**** [程序啟動] 堆內存: 4096 KB | 非堆內存: 4038 KB | cache大小: 0 | cache2大小: 0** ===== 開始測試字符串字面量 =====* [測試前] 堆內存: 4096 KB | 非堆內存: 4229 KB | cache大小: 0 | cache2大小: 0* [添加中 2] 堆內存: 4096 KB | 非堆內存: 4234 KB | cache大小: 2 | cache2大小: 0* [添加中 4] 堆內存: 4096 KB | 非堆內存: 4238 KB | cache大小: 4 | cache2大小: 0* [添加中 6] 堆內存: 4096 KB | 非堆內存: 4239 KB | cache大小: 6 | cache2大小: 0* [添加中 8] 堆內存: 4096 KB | 非堆內存: 4244 KB | cache大小: 8 | cache2大小: 0* [添加中 10] 堆內存: 4096 KB | 非堆內存: 4246 KB | cache大小: 10 | cache2大小: 0* [添加后] 堆內存: 4096 KB | 非堆內存: 4246 KB | cache大小: 10 | cache2大小: 0* 字面量測試完成** 觸發GC...* [GC前] 堆內存: 4096 KB | 非堆內存: 4247 KB | cache大小: 10 | cache2大小: 0* [GC后] 堆內存: 4076 KB | 非堆內存: 4250 KB | cache大小: 10 | cache2大小: 0** ===== 開始測試 new String() =====* [測試前] 堆內存: 4076 KB | 非堆內存: 4254 KB | cache大小: 10 | cache2大小: 0* [添加中 0] 堆內存: 4076 KB | 非堆內存: 4402 KB | cache大小: 10 | cache2大小: 1* [添加中 2] 堆內存: 4076 KB | 非堆內存: 4408 KB | cache大小: 10 | cache2大小: 3* [添加中 4] 堆內存: 4076 KB | 非堆內存: 4410 KB | cache大小: 10 | cache2大小: 5* [添加中 6] 堆內存: 4076 KB | 非堆內存: 4411 KB | cache大小: 10 | cache2大小: 7* [添加中 8] 堆內存: 4076 KB | 非堆內存: 4413 KB | cache大小: 10 | cache2大小: 9* [添加后] 堆內存: 4076 KB | 非堆內存: 4416 KB | cache大小: 10 | cache2大小: 10* new String() 測試完成** 觸發GC...* [GC前] 堆內存: 4076 KB | 非堆內存: 4420 KB | cache大小: 10 | cache2大小: 10* [GC后] 堆內存: 4956 KB | 非堆內存: 4899 KB | cache大小: 10 | cache2大小: 0** ===== 最終內存報告 =====* [最終狀態] 堆內存: 4956 KB | 非堆內存: 4900 KB | cache大小: 10 | cache2大小: 0* cache 大小: 10* cache2 大小: 0* @param args*/public static void main(String[] args) {printMemoryStats("程序啟動");testStringLiteral(); // 內存持續增長forceGCAndWait();testNewString(); // 內存穩定forceGCAndWait();// 最終報告System.out.println("\n===== 最終內存報告 =====");printMemoryStats("最終狀態");System.out.println("cache 大小: " + cache.size());System.out.println("cache2 大小: " + cache2.size());}
四、虛引用
說明
虛引用是4個引用中弱最的引用類型,它通過PhantomReference
類創建的,并且必須需要配合ReferenceQueue隊列使用
,通過虛引用的get()獲取對象總是返回null,在對象被GC回收時ReferenceQueue隊列可以收到回收通知,如下
public static void main(String[] args) throws InterruptedException { // 1. 創建引用隊列(用于接收被回收對象的虛引用)ReferenceQueue<MyResource> queue = new ReferenceQueue<>();// 2. 創建資源對象MyResource myResource = new MyResource("重要資源");// 3. 創建虛引用,關聯資源對象和引用隊列PhantomReference<MyResource> phantomRef =new PhantomReference<>(myResource, queue);System.out.println("初始狀態:");System.out.println(" 資源對象: " + myResource);System.out.println(" 虛引用是否指向對象: " + (phantomRef.get() != null)); // 虛引用總是返回nullSystem.out.println(" 引用隊列是否有數據: " + (queue.poll() != null));System.out.println();// 4. 斷開強引用,使資源對象可被回收myResource = null;// 5. 請求垃圾回收(注意:這只是建議,不保證立即執行),多次請求GC(增加成功率)for (int i = 0; i < 3; i++) {System.gc();System.runFinalization();}// 6. 給GC一點時間執行Thread.sleep(500);System.out.println("GC后狀態:");System.out.println(" 虛引用是否指向對象: " + (phantomRef.get() != null));// 7. 檢查引用隊列(虛引用會在對象回收后被加入隊列),阻塞方式等待虛引用入隊(最多等待2秒)PhantomReference<?> refFromQueue = (PhantomReference<?>) queue.remove(2000);if (refFromQueue != null) {System.out.println("? 檢測到資源已被回收,虛引用進入隊列");System.out.println(" 隊列中的引用: " + refFromQueue);System.out.println(" 是否與原始虛引用相同: " + (refFromQueue == phantomRef));// 這里可以執行資源清理操作// resource 普通堆內存對象,JVM 自動回收這個對象占用的堆內存,虛引用入隊只是通知你"對象已被回收,不需要手動釋放堆內存// System.out.println("執行清理操作:釋放資源關聯的內存...");} else {System.out.println("? 超過等待時間仍未檢測到資源回收");System.out.println("可能原因:");System.out.println("1. GC尚未執行完成");System.out.println("2. 對象仍有強引用");System.out.println("3. JVM忽略了System.gc()");}}static class MyResource {private final String name;public MyResource(String name) {this.name = name;System.out.println("創建資源: " + name);}@Overrideprotected void finalize() throws Throwable {System.out.println("🔥 垃圾回收器正在回收資源: " + name);super.finalize();}}
使用場景舉例
在精確控制資源釋放的場景經常使用,在日常業務開發中較少直接使用,但在基礎框架、中間件、高性能庫中(如Netty、JDK NIO等)有不可替代的作用
在虛引用機制中,虛引用本身不負責釋放內存,它的核心作用是提供對象被回收的通知時機,讓開發者有機會執行自定義的清理邏輯。是否需要手動釋放內存取決于資源的類型,如下:
內存類型 | 釋放責任方 | 虛引用中的作用 |
---|---|---|
堆內存 | JVM自動回收 | 無需處理 |
堆外內存 | 開發者手動釋放 | 在清理回調中釋放 |
其他資源 | 開發者手動釋放 | 在清理回調中關閉/釋放 |
例子:
/*** 虛引用例子*/
public class PhantomReferenceExample2 {// 資源清理接口@FunctionalInterfacepublic interface ResourceCleaner {void clean();}// 自定義虛引用(攜帶清理邏輯)private static class MemoryReference extends PhantomReference<ByteBuffer> {private final ResourceCleaner cleaner;private final Object jdkCleaner; // 改為 Object 類型private final int size;public MemoryReference(ByteBuffer referent,ReferenceQueue<? super ByteBuffer> queue,ResourceCleaner cleaner,Object jdkCleaner, // 改為 Objectint size) {super(referent, queue);this.cleaner = cleaner;this.jdkCleaner = jdkCleaner;this.size = size;}public void clean() {cleaner.clean(); // 執行自定義清理邏輯cleanMemory(); // 實際釋放堆外內存System.out.printf("? 釋放堆外內存: 大小=%,d bytes\n", size);}private void cleanMemory() {try {// 通過反射調用 clean() 方法Method cleanMethod = jdkCleaner.getClass().getMethod("clean");cleanMethod.invoke(jdkCleaner);} catch (Exception e) {throw new RuntimeException("無法調用clean方法", e);}}}// 內存管理器public static class MemoryManager implements AutoCloseable {private final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();private final ConcurrentHashMap<MemoryReference, Boolean> refs = new ConcurrentHashMap<>();private final ExecutorService cleanerThread = Executors.newSingleThreadExecutor();private volatile boolean running = true;public MemoryManager() {// 啟動后臺清理線程cleanerThread.submit(() -> {while (running || !refs.isEmpty()) {try {MemoryReference ref = (MemoryReference) queue.remove(500);if (ref != null) {ref.clean();refs.remove(ref);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});}/*** 分配堆外內存并返回ByteBuffer*/public ByteBuffer allocateDirect(int size) {// 分配堆外內存ByteBuffer buffer = ByteBuffer.allocateDirect(size);// 獲取JDK內置的CleanerObject jdkCleaner = getCleaner(buffer);// 創建自定義清理邏輯ResourceCleaner customCleaner = () -> {System.out.println("執行自定義清理操作...");};// 創建虛引用并注冊MemoryReference ref = new MemoryReference(buffer, queue, customCleaner, jdkCleaner, size);refs.put(ref, Boolean.TRUE);return buffer;}@Overridepublic void close() throws Exception {running = false;cleanerThread.shutdown();if (!cleanerThread.awaitTermination(5, TimeUnit.SECONDS)) {cleanerThread.shutdownNow();}System.out.println("內存管理器已關閉");}/*** 獲取ByteBuffer關聯的Cleaner*/private Object getCleaner(ByteBuffer buffer) {try {// 獲取ByteBuffer的cleaner()方法Method cleanerMethod = buffer.getClass().getMethod("cleaner");cleanerMethod.setAccessible(true);// 調用cleaner()方法獲取Cleaner實例return cleanerMethod.invoke(buffer);} catch (Exception e) {throw new RuntimeException("無法獲取Cleaner", e);}}}/**** VM options 中,需要添加兩個參數:* --add-opens java.base/java.nio=ALL-UNNAMED* --add-opens java.base/jdk.internal.ref=ALL-UNNAMED** @param args* @throws Exception*/public static void main(String[] args) throws Exception {try (MemoryManager manager = new MemoryManager()) {// 分配堆外內存,但是實例元信息還是在堆中,內存塊在堆外ByteBuffer buffer = manager.allocateDirect(1024 * 1024); // 1MB// 使用緩沖區buffer.putInt(0, 42);System.out.println("緩沖區值: " + buffer.getInt(0));// 釋放緩沖區,取消強引用,使之實例元信息可以被GC回收// 包含指向堆外內存的指針和其他元數據(注意此處不是元空間的元數據,兩者是不同概念)// 只有這個Java對象被回收后,虛引用才會被加入隊列,// 虛引用是橋梁:當堆中的 ByteBuffer 對象被回收時,虛引用會觸發回調來釋放堆外內存buffer = null;// 模擬GC,實際生產環境中,不會使用System.gc(),而是使用JVM的垃圾回收器System.gc();Thread.sleep(1000);}}
}