- 1. Where:回收哪里的東西?——JVM內存分配
-
2. Which:內存對象中誰會被回收?——GC分代思想
- 2.1 年輕代/老年代/永久代
- 2.2 內存細分
- 3. When:什么時候回收垃圾?——GC觸發條件
-
4. Why:憑什么說它是垃圾?——垃圾判斷算法
- 4.1 引用計數法
- 4.2 可達性分析法
-
5. How:如何對待垃圾?——垃圾回收算法
- 5.0 垃圾的垂死掙扎
- 5.1 標記-清除算法
- 5.2 標記-整理算法
- 5.3 復制算法
- 5.4 分代收集算法
-
6. Who:誰去處理垃圾?——垃圾回收器
- 6.1 年輕代-Serial收集器
- 6.2 年輕代-ParNew收集器
- 6.3 年輕代-Parallel Scavenge收集器
- 6.4 老年代-SerialOld收集器
- 6.5 老年代-ParallelOld收集器
- 6.6 老年代-CMS(Concurrent Mark Sweep)收集器
- 6.7 年輕代和老年代-G1(Garbage First)收集器
- 6.8 垃圾回收器對比圖
- 面試模擬
- 參考資料
1. Where:回收哪里的東西?——JVM內存分配
JVM垃圾回收機制(Garbage Collect,簡稱GC)主要負責回收JVM內存當中未被及時釋放回收的內存區域,JVM垃圾回收機制讓程序員擺脫了手動釋放內存的操作,降低了程序員疏忽大意導致的風險。
那么,垃圾回收機制到底針對哪一塊的內存空間進行處理呢?是整體內存還是某一塊內存?
在回答這個問題之前,我們需要先了解一下JVM內存分配機制,JVM內存分配機制主要有如下幾個區域:
- 棧(Stack)
- 堆(Heap)
- 方法區(Method Area)
- 本地方法棧(Native Method Stack)
- 程序計數器(PC)
我們需要知道的是,棧、本地方法棧、程序計數器和方法調用有關,是線程私有的,隨著方法結束,棧和本地方法棧的棧幀會出棧,釋放內存,因此,這三部分并不需要使用垃圾回收機制。
而堆主要存放對象實例和數組,而方法區存放類的信息、編譯信息、常量池等,這兩塊區域由于是線程共享的,在方法調用結束之后也不會被釋放內存(畢竟A用完了,不知道B、C、D會不會用到這部分)需要使用垃圾回收機制進行內存回收。
關于每個區域的具體內容,可參考博客:【后端面經-Java】JVM內存分區詳解
因此,內存回收機制主要針對堆和方法區進行處理。
2. Which:內存對象中誰會被回收?——GC分代思想
2.1 年輕代/老年代/永久代
JVM垃圾回收機制中,一般都是基于GC分代思想進行算法設計。
GC機制將內存內容分為三部分:
-
年輕代(Young Generation)
:新產生的實例基本上都處于這代,因為新產生的實例大部分都是一次性的,因此這部分內存需要經常進行內存回收; -
老年代(Old Generation)
:在新生代殘酷頻繁的篩選機制中,多次存活下來的實例會進入老年代,老年代意味著生命周期較長,一般在內存沒有滿之前不會對這部分內存進行回收; -
永久代(Permanent Generation,JCK1.8之后改成元空間(Metaspace))
:永久代存放的是JVM程序運行相關的元數據,比如類信息、方法信息、常量池等,內容重要且占空間小,因此基本上不會進行垃圾回收。
如果要類比的話,年輕代就是剛剛步入職場的小青年,不穩定性較高,很容易被裁員(垃圾回收),而熬過這個階段,成為技術骨干(老年代)之后,基本上不會在正常公司運行過程中被裁員,除非公司倒閉(內存已滿),而永久代或者元空間就是公司最高層的管理人員,對公司的運行起著關鍵作用,一般情況下不會被裁員。
2.2 內存細分
- 堆和方法區
堆中存放年輕代和老年代,而方法區中存放永久代。 - 新生代區域細分
新生代區域又分為Eden區
、Survivor0區
和Survivor1區
,比例為8:1:1
。
整體內存細分情況如圖所示:
3. When:什么時候回收垃圾?——GC觸發條件
GC按照觸發條件,可分為Scavenge GC
和Full GC
。
- Scavenge GC(Minor GC)
Scavenge GC是指年輕代Eden區的垃圾回收,觸發條件為:- 年輕代Eden區域內存不足
- 調用Scavenge GC之后,將未清理的元素放入Survivor0區域。如果Survivor0區域內存不足,則將Survivor0區域內存中的所有元素放入Survivor1區域。清空Survivor0區域,然后將Survivor1中的元素放入Survivor0中;
- 如果Survivor1區域內存不足,則將Survivor1區域內存中的所有元素放入老年代中;
- 如果老年代也內存不足,則會考慮觸發Full GC。
- 年輕代Eden區域內存不足
- Full GC(Major GC)
Full GC是整體對內存的垃圾回收,包括年輕代和老年代,觸發條件為:- 老年代內存不足
- 永久代內存不足
- 顯性調用
system.gc()
- 上次GC之后堆內存的劃分出現變化
對于GC線程,其本身的優先級比較低,因此在CPU空閑的時候,可能會進行GC處理,而在忙時基本上不會進行GC處理,除非此時內存空間不足,需要GC處理之后才能正常運行。
Full GC對于計算資源是一個很大的消耗,應該盡量避免使用Full GC。
4. Why:憑什么說它是垃圾?——垃圾判斷算法
前面主要介紹了JVM垃圾回收機制的針對對象,GC分代思想和觸發條件。那么,好好的一個對象實例,GC機制空口無憑憑什么說它是垃圾呢?這就需要垃圾判斷算法了。
常見的垃圾判斷方法有兩種:引用計數法
和可達性分析法
。
4.1 引用計數法
- 每個對象實例都有一個引用計數器,當有一個引用指向該對象實例時,引用計數器加1,當引用失效時,引用計數器減1,當引用計數器為0時,該對象實例就是垃圾,需要進行回收。
- 補充一下JVM的引用類型,如下圖所示:
- 優點:判斷邏輯簡單;
- 缺點:無法解決
循環引用
的問題(從圖論角度來說就是環狀節點無法識別)。
4.2 可達性分析法
- 將每個實例看作節點,兩個實例之間的引用關系看作路徑。從GC Roots開始,對堆內存中的對象進行遍歷,如果某個對象實例沒有被遍歷到,則說明該對象實例不可達,不可達則是垃圾,對其進行垃圾標記,等待后續回收(非連通圖查找連通子圖的數量)。
- GC Roots指的是正在運行的程序中一些基本對象,從這些對象往下查找其引用對象,包括如下幾種:
-
虛擬機棧
(棧幀中的本地變量表)中引用的對象 -
方法區
中類靜態屬性引用的對象 -
方法區
中常量引用的對象 -
本地方法棧
中JNI(Native方法)引用的對象
-
- 優點:能有效解決循環引用的問題;
- 缺點:判斷邏輯復雜,需要遍歷整個內存空間,效率較低。
5. How:如何對待垃圾?——垃圾回收算法
常見的垃圾回收算法包括:標記-清除算法
、標記-整理算法
、復制算法
和分代收集算法
(自適應算法)。
5.0 垃圾的垂死掙扎
在被處決之前,垃圾會進行一次垂死掙扎,實例第一次被標記為垃圾之后,如果可以進行一次有效finalize()
方法調用,和其他實例建立引用,那么該實例就會被復活,不會被回收。
5.1 標記-清除算法
在垃圾判斷算法執行完成后,已經被明確判斷成垃圾的實例,清除法在原地釋放其內存空間,將其標記為可用空間,等待后續的內存分配。
- 優點:操作簡單,原地清除不需要復制內存
- 缺點:會產生內存碎片,長期運行會影響CPU運行效率
5.2 標記-整理算法
標記-整理算法在標記完成之后,將所有存活的實例移動到一端,然后清除掉另一端的內存空間,這樣就可以有效解決內存碎片的問題。
- 優點:無內存碎片;
- 缺點:需要移動實例,效率較低;
5.3 復制算法
復制算法將內存空間分為兩塊,每次只使用其中一塊,當一塊內存空間內存滿了之后,將存活的實例復制到另一塊內存空間中,然后清除掉之前的內存空間。
- 優點:無內存碎片,操作簡單;標記和復制可并行;
- 缺點:可用內存空間直接減半,內存利用率較低;
5.4 分代收集算法
針對不同代的數據特點,使用不同的垃圾回收算法。
- 年輕代:復制算法
- 存活對象較少,復制算法每次的對象復制不會有太大負擔;
- 操作頻繁,復制算法標記和復制可并發處理,效率較高;
- 老年代:標記-整理算法
- 存活對象較多,減少內存碎片,提高可用性
- 永久代(元空間):不作考慮
6. Who:誰去處理垃圾?——垃圾回收器
垃圾回收器是垃圾回收算法的執行者,常見的垃圾回收器如下圖所示:
連線部分說明這兩個垃圾回收器能夠搭配使用。
6.1 年輕代-Serial收集器
- 回收算法:復制算法
- 單線程:執行回收算法的時候是單線程;適用于并發能力較低的系統。
- Stop the World:一般線程和回收線程無法并行,執行回收算法需要中斷其他線程,這個現象稱為Stop the World(亂入Dio的“咋瓦魯多”)。
- Stop the World現象會導致系統暫停,引出
垃圾收集停頓時間
這一參數,影響用戶體驗,因此需要盡量避免;
- Stop the World現象會導致系統暫停,引出
- 優點:簡單高效,適用于單核CPU;
- 缺點:無法并行,且單線程處理效率較低;
- 啟用方式:
-XX:+UseSerialGC
6.2 年輕代-ParNew收集器
- 回收算法:復制算法
- 多線程:執行回收算法的時候是多線程;
- 也會存在Stop the World現象,除了多線程的改進之外,和Serial收集器沒有太大區別;
- 優點:多線程處理效率高,適用于多核CPU;
- 缺點:一般線程和回收線程無法并行處理;
- 啟用方式:
-XX:+UseParNewGC
6.3 年輕代-Parallel Scavenge收集器
- 回收算法:復制算法
- 關注吞吐量
吞吐量 = 用戶線程運行時間 / (用戶線程運行時間 + 垃圾回收線程運行時間)
- 相關參數
-
- 垃圾收集停頓時間
- 設置方式:
-XX:MaxGCPauseMillis=一個數值
- 停頓時間過大將會直接影響每次垃圾回收的用戶體驗,停頓時間過小則會導致垃圾回收頻繁;
-
- 吞吐量大小
- 設置方式:
-XX:GCTimeRatio=一個數值
- 默認取值為
99%
,表示只有1%
的時間用于垃圾回收;
-
- 自適應模式
- 設置方式:
-XX:+UseAdaptiveSizePolicy
- 設置自適應模式之后,內存中的新生代分配比例和老年代的時間參數可自行調整,達到吞吐量、停頓時間和內存占用的平衡;
-
- 是JDK1.8的默認垃圾回收器
- 啟用方式:
-XX:+UseParallelGC
6.4 老年代-SerialOld收集器
- 回收算法:標記-整理算法
- 單線程,可類比年輕代的Serial收集器,優缺點同理
- CMS收集器的后備算法
6.5 老年代-ParallelOld收集器
- 回收算法:標記-壓縮算法
- 關注吞吐量,可類比年輕代的Parallel Scavenge收集器
- 多線程,線程數通過
-XX:ParallelGCThreads
設置,默認為CPU核心數 - 啟用方式:
-XX:+UseParallelOldGC
6.6 老年代-CMS(Concurrent Mark Sweep)收集器
- 回收算法:標記-清除算法
- 關注停頓時間,期望有較短的垃圾回收停頓時間,從而優化用戶體驗;
- 回收步驟:
- 初始標記:適用可達性分析法的思想,標記GC Roots能直接關聯到的對象,速度較快;
- 并發標記:一般線程和回收線程并行,對此時標記狀態出現變化的實例進行統計;
- 重新標記:根據并發標記的結果,對標記狀態發生變化的實例進行重新標記,這一步相對較慢;
- 并發清除:清理垃圾實例,釋放內存空間;這一步可以和一般線程并行;
- 相關參數:
- 觸發閾值:
- 設置方式:
-XX:CMSInitiatingOccupancyFraction=一個數值
- 和之前討論的何時進行垃圾回收的觸發機制不同,CMS在處理老年代的時候,不會等到內存完全占滿,而是會設置一個閾值,默認數值為
68%
,占用內存空間超過這個閾值就進行垃圾回收處理。
- 設置方式:
- 整理標記
- 設置方式:
-XX:+UseCMSCompactAtFullCollection
- 如果設置這一標記,則垃圾回收之后會進行一次整理,合并內存碎片。
- 設置方式:
- 觸發閾值:
- 優點:并發收集,提高執行效率;減少停頓時間,用戶體驗佳
- 缺點:無法處理浮動垃圾,對CPU資源敏感,且標記清除算法會產生內存碎片
6.7 年輕代和老年代-G1(Garbage First)收集器
- 回收算法:整體來看是標記-整理算法,局部來看是復制算法
- 關注停頓時間,也關注高吞吐量(我全都要.jpg);
-
分區:不同于之前所討論的分代劃分內存區域的方式,G1回收器將內存劃分為一個又一個單元區域,稱為
Region
,- 設置方式:
-XX:G1HeapRegionSize=一個數值
- 每個分區內部可以存放年輕代或者老年代的數據,根據不同的存放數據,將分區劃分為四類:
-
E
-Eden區
:存放年輕代當中Eden區域的數據; -
S
-Survivor區
:存放年輕代當中Survivor區域(survivor0和survivor1)的數據; -
O
-Old
:存放老年代的一般數據; -
H
-Humongous
:存放老年代當中大對象的數據;當占據整個Region一半以上的時候,就會被劃分為Humongous區域;
-
- 設置方式:
- 停頓預測模型:用戶可以自行設置垃圾回收停頓時間,而G1回收器會根據歷史數據構建預測模型,考慮為了滿足用戶設置的停頓時間,本次垃圾回收可以處理哪幾個Region。
-
Region優先級隊列:G1瀏覽器維護一個Region隊列,高價值的Region有更高的優先級,在垃圾回收的時候優先處理,這也是
Garbage First
名字的由來。 - 回收步驟(和CMS回收器類似):
- 初始標記
- 并發標記
- 重新標記
- 并發清除
- 優點:并發操作,并行收集,提高執行效率;關注停頓時間,可預測停頓時間,優化用戶體驗;可處理浮動垃圾,不會產生內存碎片;
6.8 垃圾回收器對比圖
對上述垃圾回收器的對比如下所示:
面試模擬
Q:介紹一下JVM和垃圾回收機制。
A:從"where"、"whice"、"when"、"why"、"how"、"who"的角度,重點介紹觸發機制
/判斷算法
/垃圾回收算法
/垃圾回收機制
。
參考資料
- JVM之垃圾回收機制(GC)
- JVM的垃圾回收機制 總結(垃圾收集、回收算法、垃圾回收器)
- 深入理解 JVM 垃圾回收機制及其實現原理