Java垃圾收集之對(duì)象存活判定、回收流程與內(nèi)存策略詳解
一、引言
在 Java 技術(shù)體系里,垃圾收集器(Garbage Collection,GC)與內(nèi)存分配策略是自動(dòng)內(nèi)存管理的核心支撐。深入探究其原理與機(jī)制,對(duì)優(yōu)化程序內(nèi)存性能、規(guī)避內(nèi)存泄漏與溢出等問題意義重大,是理解 Java 運(yùn)行時(shí)環(huán)境的關(guān)鍵環(huán)節(jié)
二、GC 基礎(chǔ)與核心問題
(一)GC 概念溯源
垃圾收集技術(shù)并非 Java 首創(chuàng),早在 1960 年,Lisp 語言已應(yīng)用內(nèi)存動(dòng)態(tài)分配與垃圾收集。GC 需解決三個(gè)核心問題:
- 識(shí)別待回收內(nèi)存:確定哪些對(duì)象已 “死亡”,即無被使用可能。
- 抉擇回收時(shí)機(jī):依據(jù)內(nèi)存使用狀況,選擇合適時(shí)機(jī)觸發(fā)回收。
- 選定回收方式:不同垃圾收集器采用各異的回收算法與實(shí)現(xiàn)邏輯 。
(二)對(duì)象存活判定算法
1. 引用計(jì)數(shù)算法
原理為給對(duì)象綁定引用計(jì)數(shù)器,引用建立時(shí)計(jì)數(shù)器加 1,引用失效時(shí)減 1,計(jì)數(shù)器為 0 則判定對(duì)象可回收。但存在循環(huán)引用缺陷,如代碼所示:
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
objA 與 objB相互引用,雖無實(shí)際訪問路徑,但引用計(jì)數(shù)不為 0,算法無法回收,故主流 Java 虛擬機(jī)棄用該算法。
2. 可達(dá)性分析算法
主流商用語言(如 Java、C#)采用的對(duì)象存活判定算法。以 “GC Roots” 為起始節(jié)點(diǎn)集,依據(jù)引用關(guān)系遍歷搜索,無引用鏈連接的對(duì)象判定為可回收。Java 中,GC Roots 涵蓋:
- 虛擬機(jī)棧本地變量表引用對(duì)象,如方法內(nèi)參數(shù)、局部變量。
- 方法區(qū)靜態(tài)屬性與常量引用對(duì)象,像類的靜態(tài)變量、字符串常量池引用。
- 本地方法棧 JNI(Native 方法)引用對(duì)象。
- 虛擬機(jī)內(nèi)部核心引用,包括 Class 對(duì)象、常駐異常對(duì)象(如
NullPointerException)等 。
三、引用分類及特性(JDK 1.2+)
JDK 1.2 拓展引用概念,按強(qiáng)度分為四類,各有獨(dú)特內(nèi)存管理行為:
(一)強(qiáng)引用
程序中最常見,如 Object obj = new Object() 。只要引用有效,對(duì)象不會(huì)被回收,是對(duì)象強(qiáng)存活的保障,支撐程序基本對(duì)象引用邏輯。
(二)軟引用
用于描述非必需但仍具使用價(jià)值的對(duì)象,內(nèi)存不足即將拋出溢出異常前,會(huì)觸發(fā)軟引用對(duì)象回收。通過 SoftReference 實(shí)現(xiàn),代碼示例:
SoftReference<Object> softRef = new SoftReference<>(new Object()); Object obj = softRef.get();
適用于緩存場景,內(nèi)存緊張時(shí)釋放非必需對(duì)象,平衡內(nèi)存使用與功能需求。
(三)弱引用
強(qiáng)度弱于軟引用,垃圾收集時(shí),無論內(nèi)存是否充足,弱引用關(guān)聯(lián)對(duì)象都會(huì)被回收。借助 WeakReference 實(shí)現(xiàn):
WeakReference<Object> weakRef = new WeakReference<>(new Object()); Object obj = weakRef.get();
常用于弱關(guān)聯(lián)對(duì)象管理,如觀察者模式中臨時(shí)關(guān)聯(lián),避免因?qū)ο笕跻脤?dǎo)致內(nèi)存無法釋放。
(四)虛引用
又稱幽靈 / 幻影引用,不影響對(duì)象生命周期,也無法通過其獲取對(duì)象,主要用于接收對(duì)象回收系統(tǒng)通知,由 PhantomReference 實(shí)現(xiàn)。是內(nèi)存回收事件監(jiān)聽的特殊手段,可在對(duì)象回收時(shí)執(zhí)行特定資源清理等操作 。
四、對(duì)象回收流程與機(jī)制
對(duì)象經(jīng)可達(dá)性分析判定為不可達(dá)后,需歷經(jīng)兩次標(biāo)記才會(huì)被回收:
(一)首次標(biāo)記與篩選
對(duì)象無 GC Roots 引用鏈時(shí),觸發(fā)首次標(biāo)記。隨后篩選是否需執(zhí)行 finalize() 方法,未重寫該方法或方法已執(zhí)行過的對(duì)象,直接判定為可回收。
(二)二次標(biāo)記與挽救
需執(zhí)行 finalize() 的對(duì)象,被移入F - Queue隊(duì)列,由 Finalizer 線程執(zhí)行該方法。若對(duì)象在 finalize() 中重新建立引用鏈(如關(guān)聯(lián)到類變量 ),二次標(biāo)記時(shí)會(huì)被移出回收隊(duì)列;否則,執(zhí)行回收。但 finalize() 存在運(yùn)行代價(jià)高、不確定性大、無法保證調(diào)用順序等問題,推薦以 try - finally 替代,示例代碼展現(xiàn)對(duì)象自我拯救過程:
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, i am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
// 首次拯救邏輯
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
// 二次拯救(失敗,因 finalize 僅執(zhí)行一次)
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
}
}
五、方法區(qū)的回收機(jī)制
方法區(qū)(如 HotSpot 的元空間 / 永久代 )常被誤解為無垃圾收集,實(shí)則不然,其回收聚焦以下兩部分:
(一)常量池回收
若常量池中的常量(如字符串 )無對(duì)象引用,且虛擬機(jī)無其他引用,可被回收。如字符串 “java” ,若系統(tǒng)無對(duì)應(yīng)引用,內(nèi)存回收時(shí)可能被清理,優(yōu)化常量池內(nèi)存占用。
(二)無用類回收
判定類為 “無用” 需滿足三個(gè)條件:
- 類及派生子類無實(shí)例,Java 堆中不存在該類相關(guān)實(shí)例。
- 加載該類的類加載器被回收,此條件在自定義類加載器場景中較難滿足,需精心設(shè)計(jì)。
- 該類對(duì)應(yīng)的
java.lang.Class對(duì)象無引用,無法通過反射訪問類方法。
Java 虛擬機(jī)允許回收滿足條件的類,但非強(qiáng)制。在反射、動(dòng)態(tài)代理等場景,需虛擬機(jī)具備類型卸載能力,可通過 -verbose:class、-XX:+TraceClassLoading、-XX:+TraceClassUnLoading(部分需 FastDebug 版支持 )查看類加載 / 卸載信息,保障方法區(qū)內(nèi)存健康。
六、結(jié)論
Java 垃圾收集器與內(nèi)存分配策略,構(gòu)建起自動(dòng)內(nèi)存管理的核心體系。從對(duì)象存活判定的算法演進(jìn),到引用分類的精細(xì)管控,再到對(duì)象回收流程的嚴(yán)謹(jǐn)執(zhí)行與方法區(qū)回收的特殊處理,共同保障程序內(nèi)存高效利用。深入理解該體系,是優(yōu)化程序性能、解決內(nèi)存問題的關(guān)鍵。后續(xù)將進(jìn)一步探究具體垃圾收集器(如 Serial、G1 等 )的實(shí)現(xiàn)與內(nèi)存分配策略細(xì)節(jié),持續(xù)深化 Java 內(nèi)存管理研究,為 Java 開發(fā)者筑牢技術(shù)根基,助力打造更高效、穩(wěn)定的Java應(yīng)用 。
到此這篇關(guān)于Java垃圾收集之對(duì)象存活判定、回收流程與內(nèi)存策略的文章就介紹到這了,更多相關(guān)Java對(duì)象存活判定、回收流程與內(nèi)存策略內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中使用Thread類和Runnable接口實(shí)現(xiàn)多線程的區(qū)別
這篇文章主要介紹了使用Thread類和Runnable接口實(shí)現(xiàn)多線程的區(qū)別,本文給大家介紹了兩種實(shí)現(xiàn)方式的步驟,除了以上兩種多線程實(shí)現(xiàn)方式,還可以使用 Callable 接口實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)
本篇文章主要介紹了詳解Java實(shí)現(xiàn)緩存(LRU,FIFO) ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
IDEA中使用jclasslib插件可視化方式查看類字節(jié)碼的過程詳解
查看JAVA字節(jié)碼有兩種方式一種是使用 jdk命令 javap,還有一種就是 使用 插件了,今天給大家分享IDEA中使用jclasslib插件可視化方式查看類字節(jié)碼的過程詳解,感興趣的朋友跟隨小編一起看看吧2021-05-05
Java實(shí)現(xiàn)格式化打印慢SQL日志的方法詳解
不管我們使用何種語言開發(fā),一旦程序發(fā)生異常,日志是一個(gè)很重要的數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)格式化打印慢SQL日志的相關(guān)資料,需要的朋友可以參考下2022-10-10

