【Java 底層】JVM 垃圾回收機制深度剖析:從對象生死判定到收集器實戰

【Java 底層】JVM 垃圾回收機制深度剖析:從對象生死判定到收集器實戰

【Java 底層】JVM 垃圾回收機制深度剖析:從對象生死判定到收集器實戰

Java 之所以被稱為 “開發效率利器”,很大程度上得益于其自動內存管理機制 —— 開發者無需手動分配和釋放內存,一切由 JVM 垃圾回收器(Garbage Collector)自動完成。但 “自動” 不代表 “無感知”:內存泄漏、OOM 異常、GC 頻繁卡頓等問題,本質上都是對垃圾回收機制理解不深導致的。

本文將從 “如何判斷對象該回收”“用什么算法回收”“不同場景選哪種回收器” 三個維度,深入拆解 JVM 垃圾回收的底層邏輯,幫你從根源上解決內存相關問題。

一、前置問題:JVM 為什么需要垃圾回收?

在 C/C++ 中,開發者需要手動調用 malloc() 分配內存、free() 釋放內存,一旦遺漏 free() 就會導致內存泄漏(無用內存占用不釋放),最終可能引發內存溢出(OOM)。

Java 通過垃圾回收器解決了這個問題:

  • 自動識別 “不再被使用的對象”(垃圾);

  • 自動釋放這些對象占用的內存;

  • 優化內存碎片,提高內存利用率。

但垃圾回收并非 “免費午餐”—— 回收過程會暫停應用線程(STW,Stop-The-World),頻繁或過長的 STW 會導致應用卡頓。因此,垃圾回收的核心目標是:在盡可能短的 STW 時間內,高效回收無用內存

二、第一步:如何判定對象 “已死”?兩種核心算法的博弈

垃圾回收的前提是 “識別垃圾”。JVM 采用兩種主流算法判斷對象是否存活:引用計數法和可達性分析。

1. 引用計數法:簡單但 “有漏洞” 的方案

原理:給每個對象設置一個 “引用計數器”,當有地方引用該對象時,計數器 + 1;引用失效時,計數器 - 1。當計數器為 0 時,認為對象可回收。

優點:實現簡單,判斷效率高。

缺點:無法解決 “循環引用” 問題。例如:

class A {B b;
}
class B {A a;
}public static void main(String[] args) {A a = new A();B b = new B();a.b = b; // A 引用 Bb.a = a; // B 引用 Aa = null; // 取消外部對 A 的引用b = null; // 取消外部對 B 的引用
}

此時 A 和 B 的引用計數器都是 1(互相引用),但已無外部引用,理應被回收,可引用計數法會誤判為 “存活”,導致內存泄漏。

因此,JVM 未采用引用計數法,而是選擇了更可靠的可達性分析。

2. 可達性分析法:JVM 的 “標準方案”

原理:以 “GC Roots” 為起點,向下搜索引用鏈(Reference Chain)。如果一個對象到 GC Roots 沒有任何引用鏈相連(即不可達),則認為該對象可回收。

GC Roots 包含哪些對象?

  • 虛擬機棧中引用的對象(如局部變量、方法參數);

  • 方法區中類靜態屬性引用的對象(如 static 變量);

  • 方法區中常量引用的對象(如 final 變量);

  • 本地方法棧中 JNI(Native 方法)引用的對象。

示例:上述 A 和 B 的循環引用案例中,a 和 b 被置為 null 后,A 和 B 到 GC Roots 均無引用鏈,因此會被判定為可回收,解決了循環引用問題。

3. 補充:引用的 “強度分級”

Java 中的 “引用” 并非非黑即白。JDK 1.2 后,引用被分為四級,強度從強到弱依次為:

  • 強引用:普通引用(如 Object obj = new Object()),只要強引用存在,對象永遠不會被回收;

  • 軟引用SoftReference 包裝,內存不足時才會被回收(適合緩存場景);

  • 弱引用WeakReference 包裝,下次 GC 時必定被回收(適合臨時數據);

  • 虛引用PhantomReference 包裝,唯一作用是在對象被回收時收到通知(幾乎不用)。

這四種引用讓垃圾回收更靈活 —— 例如,緩存數據可用軟引用,既保證內存不足時自動釋放,又能在有內存時保留緩存。

三、第二步:如何回收?三大核心算法與內存整理

判定對象 “已死” 后,需要回收其占用的內存。JVM 采用三種經典回收算法,各有適用場景。

1. 標記 - 清除算法(Mark-Sweep):最基礎但 “有瑕疵”

步驟

  1. 標記:通過可達性分析,標記所有可回收的對象;

  2. 清除:遍歷內存,回收所有被標記的對象,釋放內存空間。

優點:實現簡單,不需要移動對象。

缺點

  • 產生內存碎片:回收后內存中會出現大量不連續的空閑區域,當需要分配大對象時,可能因找不到足夠大的連續空間而觸發新的 GC;

  • 效率較低:標記和清除過程都需要遍歷大量對象。

2. 復制算法(Copying):犧牲空間換效率

步驟

  1. 將內存分為大小相等的兩塊(From 區和 To 區);

  2. 只使用 From 區,To 區空閑;

  3. GC 時,將 From 區中存活的對象復制到 To 區(按順序排列,無碎片);

  4. 清空 From 區,交換 From 和 To 區的角色,重復使用。

優點

  • 無內存碎片;

  • 回收效率高(只需復制存活對象,存活對象少的時候效率極高)。

缺點

  • 內存利用率低(僅能使用一半內存);

  • 不適合存活對象多的場景(如老年代,復制成本太高)。

應用:JVM 年輕代(Eden 區和 Survivor 區)主要采用復制算法。年輕代中對象存活率低,復制成本小,且通過 “Eden + 2 個 Survivor 區(8:1:1)” 的設計,將內存浪費控制在 10%(只留 1 個 Survivor 區空閑)。

3. 標記 - 整理算法(Mark-Compact):老年代的 “專屬方案”

步驟

  1. 標記:同標記 - 清除算法,標記可回收對象;

  2. 整理:將所有存活對象向內存一端移動,然后直接清理掉邊界外的內存。

優點

  • 無內存碎片;

  • 內存利用率 100%(無需犧牲一半空間)。

缺點

  • 增加了 “移動對象” 的成本,效率比復制算法低。

應用:JVM 老年代主要采用標記 - 整理算法。老年代中對象存活率高,移動成本雖高,但避免了內存碎片和空間浪費,是更平衡的選擇。

四、第三步:誰來執行回收?經典垃圾收集器的 “看家本領”

垃圾收集器是算法的具體實現。JVM 提供了多種收集器,各有側重,需根據應用場景選擇。

1. Serial GC:單線程回收,簡單高效但卡頓明顯

特點

  • 單線程執行 GC,GC 時暫停所有應用線程(STW);

  • 采用 “復制算法(年輕代)+ 標記 - 整理算法(老年代)”;

  • 實現簡單,內存占用少,適合單核 CPU 或小內存應用(如嵌入式設備)。

缺點:STW 時間長(尤其老年代回收時),不適合大內存、高并發場景。

2. Parallel GC:多線程 “吞吐量優先”

特點

  • 多線程執行 GC(年輕代和老年代均用多線程),縮短 STW 時間;

  • 目標是 “高吞吐量”(吞吐量 = 運行用戶代碼時間 / 總時間);

  • 可通過參數控制吞吐量(如 -XX:MaxGCPauseMillis 限制最大 STW 時間,-XX:GCTimeRatio 控制 GC 時間占比)。

應用:適合后臺任務、批處理程序等對吞吐量敏感,對延遲要求不高的場景。

3. CMS GC:“低延遲” 的并發回收器(已逐漸被淘汰)

CMS(Concurrent Mark Sweep) 是第一款真正意義上的并發收集器,目標是 “最短 STW 時間”。

步驟(分四階段)

  1. 初始標記(STW):快速標記 GC Roots 直接關聯的對象(耗時短);

  2. 并發標記:GC 線程與應用線程并發執行,遍歷引用鏈,標記所有可達對象(耗時最長,但不阻塞應用);

  3. 重新標記(STW):修正并發標記期間因應用線程運行導致的標記變動(耗時較短);

  4. 并發清除:GC 線程與應用線程并發執行,回收被標記的對象(不阻塞應用)。

優點:STW 時間短,適合對延遲敏感的應用(如 Web 服務)。

缺點

  • 并發階段占用 CPU 資源,可能導致應用響應變慢;

  • 采用標記 - 清除算法,會產生內存碎片;

  • 對大內存支持不好(并發標記時內存占用高);

  • JDK 9 中被標記為 deprecated,JDK 14 中移除。

4. G1 GC:面向大內存的 “區域化” 收集器

G1(Garbage-First)是為替代 CMS 設計的,適合 4GB 以上大內存場景,兼顧吞吐量和延遲。

核心創新

  • 區域化內存布局:將堆內存劃分為多個大小相等的獨立區域(Region),每個區域可動態扮演年輕代或老年代,靈活分配內存;

  • Mixed GC:優先回收 “垃圾最多的區域”(Garbage-First),提高回收效率;

  • 停頓預測模型:根據歷史數據預測 STW 時間,確保不超過用戶設置的目標(如 -XX:MaxGCPauseMillis=200)。

步驟:類似 CMS,但增加了 “篩選回收” 階段,只回收垃圾多的區域,減少 STW 時間。

優點

  • 大內存下表現優異,STW 時間可控;

  • 無內存碎片(區域內采用復制算法,整體類似標記 - 整理);

  • 兼顧吞吐量和延遲,是當前主流收集器之一。

5. ZGC/Shenandoah:超低延遲的新一代收集器

JDK 11 引入 ZGC,JDK 12 引入 Shenandoah,二者均為 “低延遲、高并發” 收集器,STW 時間可控制在毫秒級甚至微秒級,適合超大內存(TB 級)場景。

核心技術

  • 著色指針:通過指針標記對象狀態(如是否被標記、是否可回收),避免傳統的 “標記 - 清除” 流程;

  • 讀屏障 / 寫屏障:在對象引用讀寫時插入少量代碼,實現并發標記和移動,幾乎不阻塞應用線程。

應用:對延遲要求極高的場景(如高頻交易、實時數據分析),但目前在生產環境中的應用不如 G1 廣泛。

五、實戰避坑:GC 問題的排查與優化思路
1. 常見問題及原因:
  • 頻繁 Full GC:可能是內存泄漏(對象長期被引用無法回收)、老年代對象增長過快(如大對象直接進入老年代);

  • STW 時間過長:收集器選擇不當(如用 Serial GC 處理大內存)、堆內存設置不合理(太大或太小);

  • OOM 異常:堆內存不足(需調大 -Xmx)、永久代 / 元空間不足(調大 -XX:MaxMetaspaceSize)。

2. 優化原則:
  • 根據場景選收集器:延遲敏感用 G1/ZGC,吞吐量優先用 Parallel GC;

  • 合理設置堆內存:避免太小(GC 頻繁)或太大(單次 GC 時間長),通常建議為物理內存的 1/2 ~ 1/4;

  • 減少大對象:大對象直接進入老年代,易觸發 Full GC,盡量拆分或復用對象;

  • 監控與調優:通過 jstatjconsole 或可視化工具(如 GCEasy)監控 GC 頻率和 STW 時間,逐步調整參數。

六、總結:垃圾回收的 “本質” 是平衡的藝術

JVM 垃圾回收的核心是 “在效率、延遲、內存利用率之間找平衡”:

  • 年輕代用復制算法,優先效率;

  • 老年代用標記 - 整理算法,優先避免碎片;

  • 收集器選擇則根據 “吞吐量” 或 “延遲” 需求,從 Serial 到 ZGC,本質是對 “并發” 和 “STW” 的權衡。

理解這些底層邏輯,不僅能解決 GC 相關問題,更能幫你寫出更 “內存友好” 的代碼 —— 比如避免創建不必要的對象、及時釋放無用引用、合理設計緩存策略等。畢竟,最好的 GC 優化,是從代碼層面減少垃圾的產生。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/100359.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/100359.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/100359.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網絡問題排查

網絡連通性測試:ping ip持續性監測:ping -t ipnetstat 可以查看網絡連接狀態,可以看到顯示系統的網絡連接,路由表,接口等信息。netstat -nult 回車-t:顯示的是tcp的連接-u:顯示udp的連接-l:只顯示監聽狀態的端口-n:顯示…

tuple/dict/list 這三個數據類型在取值時候的區別

tuple(元組)、dict(字典)、list(列表)在取值時的區別。 1. list(列表) 👉 列表就是“一串有順序的東西”,像排隊的人。 取值方式:用 下標&#xf…

深度解析大模型服務性能評測:AI Ping平臺助力開發者精準選型MaaS服務

深度解析大模型服務性能評測:AI Ping平臺助力開發者精準選型MaaS服務 🌟 Hello,我是摘星! 🌈 在彩虹般絢爛的技術棧中,我是那個永不停歇的色彩收集者。 🦋 每一個優化都是我培育的花朵&#xff…

OpenCV物體跟蹤:從理論到實戰的全面解析

? 一、引言? 在計算機視覺的廣闊領域中,物體跟蹤技術宛如一顆璀璨的明星,散發著獨特的魅力與價值,發揮著舉足輕重的作用。它致力于在連續的圖像幀或視頻序列里,精準識別并持續定位特定的目標物體,這一過程看似簡單…

【Python】OS模塊操作目錄

1、概述os模塊是一個Python內置的操作目錄和查看系統基礎信息的模塊,可用于讓我們對目錄進行批量操作,其中包括:查看系統信息(環境變量、分隔符、換行符等),對目錄進行創建、刪除、重命名、查看目錄內容等&…

JavaScript中 i++ 與 ++i

在 JavaScript 編程中,i(前置自增)和i(后置自增)是兩個常用但極易混淆的運算符。它們看似都能實現變量自增 1 的功能,但其執行時機和返回值的差異,常常導致開發者在實際編碼中出現邏輯錯誤。本文…

fastapi 中間件的使用

1. 中間件基礎結構from starlette.middleware.base import BaseHTTPMiddlewareclass RequestLoggerMiddleware(BaseHTTPMiddleware):async def dispatch(self, request: Request, call_next):# 1. 請求處理前邏輯# 2. 調用后續處理response await call_next(request)# 3. 響應…

網絡白菜包子手動安裝 Arch Linux

大家好!我是大聰明-PLUS!針對初學者的 Arch Linux 安裝詳細教程。我曾經花了不少時間才搞清楚安裝過程。本文旨在提供一種“捷徑”,讓每個人都能輕松上手,無論他們是否有 Linux 使用經驗。Arch 的主要特點是極其靈活的系統配置&am…

Linux學習筆記(五)--Linux基礎開發工具使用

在Linux中軟件包通常是指一個包含了軟件程序、元數據、依賴關系信息和安裝腳本的壓縮文件??。因為在Linux上如果沒有軟件包管理器,那么想要下載軟件會非常麻煩,不僅需要自己去手動編譯和安裝,而且難以卸載和管理,所以軟件包的出現解決了這些問題.軟件包一般是由程序文件(編譯…

數據結構(陳越,何欽銘) 第十講 排序(下)

10.1 快速排序 10.1.1 算法概述10.1.2 選主元10.1.3 子集劃分10.1.4 算法實現10.2 表排序 10.2.1 算法概述10.2.2 物理排序10.3 基數排序 10.3.1 桶排序10.3.2 基數排序10.3.3 多關鍵字的排序10.4 排序算法的比較

vue 使用print.js 打印文本,HTML元素,圖片,PDF

vue 使用print.js 打印文本,HTML元素,圖片,PDF 安裝 npm install print-js --save示例 <template><div class"print-example"><h2>Print.js 打印示例</h2><!-- 打印區域 --><div id"printableArea" class"printable…

jenkins審批機器人功能概述-Telegram版

Jenkins審批機器人 - 功能概述 代碼鏈接&#xff0c;私聊可得 項目簡介 Jenkins審批機器人是一個集成Jenkins CI/CD流程的自動化審批系統&#xff0c;通過Telegram機器人提供便捷的發布審批功能。該系統支持多環境部署審批、用戶權限管理、構建結果通知等完整的DevOps審批流程。…

Rust : 關于解引用“*”

關于解引用*操作符&#xff0c;謹供參考&#xff01; 一、主要代碼 use std::ops::Deref; fn main() {model_1();model_2();model_3();model_4();model_5();model_6();model_7();model_8();model_9(); }二、*操作符與常見的引用和解引用 fn model_1(){let reference:&St…

【高級終端Termux】在安卓手機/平板上使用Termux 搭建 Debian 環境并運行 PC 級 Linux 應用教程(含安裝WPS,VS Code)

Termux 搭建 Debian 環境并運行 PC 級 Linux 應用教程 一、前言 1. 背景 眾所周知&#xff0c;最新搭載澎湃OS和鴻蒙OS的平板都內置了PC級WPS&#xff0c;辦公效率直接拉滿&#xff08;板子終于從“泡面蓋”升級為“生產力”了&#xff09;。但問題來了&#xff1a;如果不是這…

從循環到矩陣運算:矢量化加速機器學習的秘訣

矢量化實現全解析&#xff1a;從原理到實戰 在學習數據科學、機器學習和深度學習的過程中&#xff0c;我們經常會聽到一個高頻詞——矢量化&#xff08;Vectorization&#xff09;。很多庫的官方文檔、教程里也不斷強調“要盡量使用矢量化操作&#xff0c;而不是顯式循環”。那…

大數據畢業設計-大數據-基于大數據的熱門游戲推薦與可視化系統(高分計算機畢業設計選題·定制開發·真正大數據)

&#x1f34a;作者&#xff1a;計算機編程-吉哥 &#x1f34a;簡介&#xff1a;專業從事JavaWeb程序開發&#xff0c;微信小程序開發&#xff0c;定制化項目、 源碼、代碼講解、文檔撰寫、ppt制作。做自己喜歡的事&#xff0c;生活就是快樂的。 &#x1f34a;心愿&#xff1a;點…

從零到一:用 Qt + libmodbus 做一個**靠譜**的 Modbus RTU 小工具(實戰總結)

文章目錄從零到一&#xff1a;用 Qt libmodbus 做一個**靠譜**的 Modbus RTU 小工具&#xff08;實戰總結&#xff09;你會得到什么快速背景&#xff1a;為什么是 Modbus RTU&#xff1f;協議速查&#xff08;夠用不啰嗦&#xff09;工程結構與 UI 組織連接“三板斧”&#xf…

使用Python創建本地Http服務實現與外部系統數據對接

在Python 3.10中創建一個能夠處理GET和POST請求的本地HTTP服務器&#xff0c;并提供一個默認的 index.html 頁面是完全可行的。Python的標準庫中的 http.server 模塊雖然簡單&#xff0c;但通過一些自定義擴展可以滿足這個需求。 下面我將提供一個實現方案&#xff0c;它包含一…

了解篇 | StarRocks 是個什么數據庫?

今天簡要介紹一下StarRocks 這個強大的數據庫。注意&#xff1a;本文章內容僅供個人學習&#xff0c;不包含任何推薦性質。StarRocks&#xff08;原名 Doris&#xff09;是一個高性能、全場景的MPP&#xff08;大規模并行處理&#xff09;分析型數據庫。它專為極速的多維聯機分…

SSL部署完成,https顯示連接不安全如何處理?

在部署 SSL 后&#xff0c;如果瀏覽器仍然顯示 “連接不安全” 或 “Not Secure”&#xff0c;通常是由以下幾種原因導致的。針對每種可能的原因和問題&#xff0c;以下提供了詳細的排查和解決方案。 1. 排查問題的可能原因 1.1 SSL 證書未正確安裝 如果 SSL 證書安裝不完整或…