JVM 垃圾收集是 Java 自動內存管理的核心,本文通過圍繞 “哪些是垃圾、何時回收、怎么回收、用啥回收器、內存咋分配” 等展開
一、判斷哪些是垃圾
- 引用計數法:給對象分配引用計數器,有引用時計數加 1,引用失效減 1 ,計數為 0 則可回收。但無法解決循環引用問題(如兩個對象相互引用,實際無外部引用,計數器卻不為 0 )。
Java虛擬機并不是通過引用計數算法來判斷對象是否存活的
- 可達性分析:以 GC Roots(如虛擬機棧中引用的對象、方法區靜態變量引用的對象等 )為起點,遍歷對象引用鏈,沒被鏈連接的對象視為可回收垃圾,主流 JVM 常用此方式。
- 枚舉根節點:需暫停應用線程(“stop the world” ),因遍歷期間對象引用變化會影響結果,所以要讓線程停頓,后續有優化手段(如并發標記 )緩解停頓影響。
- 引用分類:
- 強引用 程序正常引用,如?Object obj = new Object()?,只要強引用在,對象不回收
- 軟引用(內存不足時回收,可配合緩存場景 )用來描述一些還有用,但非必須的對象。只被軟引用關聯著的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
- 弱引用(垃圾收集時就回收 )也是用來描述那些非必須對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生為止。當垃圾收集器開始工作,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。
- 虛引用(主要用于跟蹤對象回收,回收前收到系統通知 )為一個對象設置虛引用關聯的唯一目的只是為了能在這個對象被收集器回收時收到一個系統通知。

即使在可達性分析算法中判定為不可達的對象,也不是“非死不可”的,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記,隨后進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。假如對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,那么虛擬機將這兩種情況都視為“沒有必要執行”。
二、?垃圾回收時機與分代收集理論
回收時機:新生代(Minor GC ):Eden 區快滿時觸發,回收新生代垃圾,借助復制算法,把存活對象移到 Survivor 或老年代;老年代(Major GC/Full GC ):老年代空間不足、永久代(元空間 )滿等情況觸發,回收老年代及可能涉及新生代,用標記 - 整理或標記 - 清除(結合壓縮 ),Full GC 耗時久,盡量避免。
分代收集建立在兩個分代假說之上:
1)弱分代假說(Weak Generational Hypothesis):絕大多數對象都是朝生夕滅的。
2)強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消亡
這兩個分代假說共同奠定了多款常用的垃圾收集器的一致的設計原則:收集器應該將Java堆劃分出不同的區域,然后將回收對象依據其年齡(年齡即對象熬過垃圾收集過程的次數)分配到不同的區域之中存儲。
設計者一般至少會把Java堆劃分為新生代(Young Generation)和老年代(Old Generation)兩個區域,在新生代中,每次垃圾收集時都發現有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。
- 大部分對象朝生夕死:新生代特點,對應 “標記復制” 算法思路,如 Eden 區 + Survivor 區,新對象先放 Eden ,回收時把存活對象復制到 Survivor ,減少內存碎片。
- 少數對象存活較久:老年代特征,常用 “標記清除”“標記整理” 算法。“標記清除” 先標記可回收對象,再清除,會產生內存碎片;“標記 - 整理” 標記后讓存活對象移動、緊湊排列,解決碎片問題,但需額外移動成本。
- 跨代引用假說:老年代對象引用新生代對象,比新生代內部引用少。垃圾收集時,若掃描老年代找跨代引用,效率低。可通過在新生代設記憶集(記錄老年代到新生代的引用 ),當發生Minor GC時,只有包含了跨代引用的小塊內存里的對象才會被加入到GC Roots進行掃描,避免全掃老年代,優化回收效率。
三、垃圾回收算法(收集算法 )
- 標記清除:分標記、清除階段,標記出可回收對象,再清理內存。缺點是1.產生碎片,可能導致大對象無法分配連續內存2.執行效率不穩定。

- 標記復制:將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。實現簡單,運行高效,無空間碎片,不過這種復制回收算法的代價是將可用內存縮小為了原來的一半

現在的商用Java虛擬機大多都優先采用了這種收集算法去回收新生代,IBM公司曾有一項專門研究對新生代“朝生夕滅”的特點做了更量化的詮釋——新生代中的對象有98%熬不過第一輪收集。因此并不需要按照1∶1的比例來劃分新生代的內存空間。
在1989年,Andrew Appel針對具備“朝生夕滅”特點的對象,提出了一種更優化的半區復制分代策略,現在稱為“Appel式回收”。
Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾搜集時,將Eden和Survivor中仍然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內存空間為整個新生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會被“浪費”的。
- 標記整理:標記后,讓存活對象向一端移動,整理內存,解決碎片問題,適用于老年代等對象存活率高區域,不過移動對象有性能開銷,還需處理對象引用更新。

- 分代收集:結合不同代特點選算法,新生代用標記 - 復制,老年代用標記 - 清除 / 標記 - 整理,是 JVM 常用策略,如 HotSpot 虛擬機的分代垃圾收集器(Serial、ParNew、Parallel Scavenge 對應新生代,Serial Old、Parallel Old、CMS、G1 等涉及老年代 )。
四、垃圾收集器(不同實現 )
了解
- Serial(串行 ):新生代、老年代都可用,單線程工作,“Stop - The - World” 明顯,簡單高效,適合客戶端模式、內存小場景。
- ParNew:Serial 多線程版,用于新生代,配合 CMS 老年代收集器(CMS 新生代需用 ParNew 或 Serial ),多線程加速新生代回收,在服務端應用常見。
- Parallel Scavenge:新生代收集器,關注吞吐量(運行用戶代碼時間 /(用戶代碼時間 + 垃圾收集時間 )),適合后臺計算等對吞吐量敏感場景,可自動調節參數(自適應調節策略 )。
- Serial Old:Serial 老年代版本,單線程,標記 - 整理算法,可與 Parallel Scavenge 配合,或作為 CMS 后備預案(CMS 并發失敗時啟用 )。
- Parallel Old:Parallel Scavenge 老年代版,多線程、標記 - 整理算法,讓吞吐量優先的收集器組合(Parallel Scavenge + Parallel Old )更完善,適合追求高吞吐量場景。
主流
- CMS(Concurrent Mark Sweep):
特點:以 “并發” 為核心,標記和清除階段可與應用線程并行(減少 stop the world 時間 );基于 “標記 - 清除” 算法。
問題:會產生內存碎片;并發階段占用 CPU 資源,可能影響應用;無法處理 “浮動垃圾”(并發階段新產生的垃圾,需等下次回收 )。
適用:追求低延遲、對吞吐量要求不極致的場景(如 Web 應用 )。
- G1(Garbage - First):
特點:面向服務端應用,把堆劃分為多個 Region;基于 “標記 - 整理”,按 Region 回收,優先回收垃圾多的 Region(“Garbage - First” );可預測停頓時間(通過設置停頓目標,規劃回收 Region )。
優勢:兼顧吞吐量和延遲,適合大內存場景;減少碎片。
適用:對停頓敏感、堆內存較大的應用(如大型后臺服務 )。
五、內存分配與回收策略
- 對象優先在 Eden 分配:新生代 Eden 區是對象誕生地,新對象先放這,Eden 滿觸發 Minor GC。
- 大對象直接進老年代:大對象(如超長數組 )不適合在新生代折騰,直接分配到老年代,避免多次 GC 拷貝。
- 長期存活的進入老年代:對象在新生代 Survivor 區經歷多次 Minor GC 仍存活(通過 “年齡計數器” 判斷 ),會晉升到老年代。
- 動態年齡判定:并非等 “年齡” 到閾值才晉升,若 Survivor 中同年齡對象總和超過該區一半,年齡≥此值的對象直接進老年代,靈活調整。
- 空間分配擔保:Minor GC 前,JVM 判斷老年代剩余空間是否夠放新生代存活對象。若預估夠,執行 Minor GC;否則看 “擔保” 機制,允許則嘗試,不允許則轉為 Full GC ,保障內存分配安全。
六、垃圾回收相關問題
- 空間碎片問題:標記 清除易產生,影響大對象分配,標記整理、標記復制可緩解,不同收集器處理方式不同(如 G1 靠 Region 劃分和整理,減少碎片影響 )。
- “Stop the world”:垃圾收集時暫停用戶線程,避免對象引用變化干擾回收,不同收集器盡力縮短停頓(如 G1 并發標記、CMS 并發階段 ),但關鍵步驟(如根節點枚舉 )仍需停頓,是垃圾收集需優化的點。
- 什么時候回收:沒有固定嚴格時間,JVM 依堆內存使用、分代特點等判斷。新生代對象滿了觸發 Minor GC ,老年代空間不足、永久代(元空間 )不足等觸發 Full GC ,不同收集器觸發機制有差異,且要平衡回收頻率和性能,避免頻繁回收影響應用。