JVM 垃圾回收 這篇文章嘗試對當前主流的JVM垃圾回收機制進行簡要介紹。 一 垃圾回收討論的範圍 JVM 的記憶體分為方法區,JVM棧,本地方法棧,堆,程式計數器等幾個部分。 其中程式計數器,JVM棧,本地方法棧三部分的生命周期與線程相同,隨著線程的回收這幾部分記憶體其生命周期自然結束而得以回收。 而 ...
JVM 垃圾回收
這篇文章嘗試對當前主流的JVM垃圾回收機制進行簡要介紹。
一 垃圾回收討論的範圍
JVM 的記憶體分為方法區,JVM棧,本地方法棧,堆,程式計數器等幾個部分。
其中程式計數器,JVM棧,本地方法棧三部分的生命周期與線程相同,隨著線程的回收這幾部分記憶體其生命周期自然結束而得以回收。
而方法區用於存儲靜態變數、常量、類信息等數據,堆更是用於創建對象時在其上劃分記憶體。隨著程式的運行,這些數據何時不再有用,需要被回收便是垃圾回收所關註的問題。
二 垃圾回收的基本策略
垃圾回收所要解決的問題便是將記憶體中無用的對象進行回收,以便釋放出記憶體空間用於後續程式運行分配空間的需要。這裡的問題主要有:
- 對象何時會無用
- 如何回收更加高效
對象何時會無用?
換句話說便是如何判斷對象不會再被任何程式使用。一種解決方案是所謂“引用計數法”,即當程式中每有一處引用該對象時,引用技術器+1,當每有一處引用失效時,對應-1,當一個對象的“引用計數器”歸零時,便認為其可以被回收。這種方案看似行之有效,但一個潛在的問題便是迴圈引用,假設A對象持有B對象的一個引用,B對象同時持有A對象的一個引用,當程式中任何其他地方不再有指向這兩個對象的引用時,A和B仍舊互相持有,這導致A和B將永遠不能被回收,試想程式中出現多處類似的現象,將極易引發記憶體溢出。
主流的程式語言中,通常採用可達性分析(Reachability Analysis)來判斷對象是否無用。可達性分析是指,從GC Roots開始進行搜索,如果一個對象沒有任何GC Roots能夠到達的話便認為該對象不再有用。對於上面所述的互相引用的情況,顯然是從GCRoots不可達的。在Java中,可能的GC Roots對象包括:
JVM棧中引用的對象
方法區中靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧中引用的對象
如何回收更加高效?
所謂高效的問題,可以理解為如何在對應用程式影響最小的情況下完成最垃圾回收。即垃圾回收應儘可能不影響程式的正常運行,同時能夠對不再有用的對象進行高效的回收以為程式的後續運行提供足夠的空間。
三 幾種垃圾收集器
1.1. Serial / Serial Old收集器
顧名思義,這是一款單線程收集器。其線上程回收時,僅有一個線程,同時所有的用戶線程均會被停止,即所謂Stop the world,顯而易見,對於要求具有較高性能的伺服器程式來說,這種情況是難以接受的。而對輕量的客戶端程式來說,如果Stop the world僅僅是毫秒或者數十毫秒級別的,那麼採用這種簡單而高效的垃圾回收器也未嘗不可。
1.2. ParNew 收集器
ParaNew收集器可視為Serial收集器的多線程版本,是一款新生代垃圾收集器。由於Parallel Scavenge收集器不能配合CMS收集器使用,ParNew收集器便成為了配合CMS使用的新生代首選收集器,當使用CMS收集器時,ParNew收集器是預設的新生代收集器。也可通過-XX:+UseParNewGC 強制指定使用該收集器。
1.3. Prarallel Scavenge收集器
此收集器也是一款新生代多線程收集器。與ParNew收集器主要不同的是,該收集器主要關註吞吐量這個參數。
吞吐量=time-of-user-program/(time-of-user-program + time-of-GC)
吞吐量越高代表系統對CPU的利用率越高,能夠將更多的時間投入到實際的計算中,這適合後臺需要大量計算而交互較少的系統。
該收集器提供-XX:MaxGCPauseMillis和-XX:GCTimeRatio兩個參數用於控制吞吐量參數。吞吐量=1/(1+GCTimeRatio)。還提供了-XX:+UseAdaptiveSizePolicy用來開啟自適應調整各記憶體分區大小
1.4. Parallel Old 收集器
Parallel Old收集器時Parallel Scavenge收集器的老年代版本,在關註吞吐量有限的應用場景中可以結合Parallel Scavenge使用
1.5. CMS(Concurrent Mark Sweep)收集器
該收集器以或多最短的停頓時間為目標,採用標記-清除演算法。回收過程可以分為四步:
a. 初始標記 initial mark
標記GC Roots能夠直接關聯到達對象,速度很快,需要Stop the world
b. 併發標記 concurrent mark
進行GC Roots Tracing, 可與用戶線程一同進行
c. 重新標記 remark
修正併發標記期間用戶程式運行導致標記產生變動的部分,並行運行,需要stop the world
d. 併發清除 concurrent seep
與用戶線程一併運行
整個過程中,1和3需要stop the world,但所占用的時間短,而2和4占用的時間長但可以與用戶線程一同進行,因此整體上對用戶程式產生的停頓影響較小。
1.6. G1(Garbage First) 收集器
G1收集器可以獨立完成新生代和老年代的垃圾回收,除了這種傳統劃分外,其將記憶體區域劃分為若幹個獨立的Region。從整體上看G1採用標記-整理演算法,而對每個Region則採用複製演算法,可以有效避免產生大量記憶體碎片導致頻繁GC。此外,G1還會評估每次回收時各Region回收的價值大小,優先回收價值高的Region。主要分為以下幾步
- 初始標記 initial mark
- 併發標記 concurrent mark
- 最終標記 final mark
- 篩選回收 live data counting and evacuation
其他步驟與CMS類似,而第四步主要評估各Region回收價值的大小和成本,按照用戶期望的代價進行回收。