Java GC - 垃圾回收機制

一、簡介算法

    對於Java developer來講,瞭解JVM GC工做原理可以幫助咱們開發出更優秀的應用,同時在處理JVM瓶頸時可以更加自由。在最近一年的應用開發中能體會到這些知識帶來的好處,而且讓咱們的應用在較大規模的併發時可以良好的工做。併發

    本文部分知識和圖片來源於書籍《Java Performance》 - Charlie Hunt & Binu John 著,該書全面講解了Java 應用的性能分析、優化點與JVM原理等知識,本文(以及稍候的一些文章)只包含 GC collector 的部分知識,另外也只是針對經常使用的HotSpot JVM,固然不少知識是相通的。性能

 

二、 JVM Overview優化

    

上圖爲JVM的概要構架,主要分爲 VM Runtime、GC Collector以及 JIT Compiler,其中咱們能經過配置干預的只有GC 和 JIT(經過-server 和 -client選擇不一樣的 JIT Compiler,其中-server在啓動完成後具備更好的性能,可是花費啓動時間稍長些 -- 雖然咱們沒明顯感受)。spa

下圖則是JVM中內存分區模型,即分爲 Heap area, method area, VM stack, native method stack和PC register。線程

本文只關注GC collector所涉及的Heap area 和 Method area, 其餘的可另外瞭解。翻譯

 

三、Garbage Collections3d

    在GC 中,最 」引人入勝「 的是 "stop-the-world",它在目前所實現的GC算法中都將出現,它出現的時候JVM 所承載的應用程序將中止執行,也就是說咱們的應用中的全部線程將中止響應,直到 "stop-the-world" 完成工做。orm

    HotSpot VM把 Heap Area分爲 young generation 和 old generation兩個物理區域,也就是咱們常翻譯爲:年輕代和年老代。它們有如下特徵:server

1. 年輕代:大部分對象在這裏分配,一般來講會進行比較小可是比較頻繁的回收,花費時間較短。

2. 年老代:內存相對較大,對象會相對保留比較長的時間,大對象也通常分配在此,佔用空間上升緩慢而且回收頻率低,"stop-the-world" 會在它回收時發生,花費較長的時間。

    以下圖中,對象在年輕代分配,通過一些時間後可能會被移到年老代。其中的permanent generation爲持久區,主要存放元數據如class data structures, interned strings等信息。GC 重點在於 young gen 和 old gen的回收。

    

 

 

 

3.1 Young generation

    Young generation 有更細的劃分,分爲 Eden 和 2個survivor space(即倖存區) 以下圖:

  1. The eden: 大部分新對象在這裏分配,有些大對象直接在年老區分配,這個區域進行回收後不少狀況下會變空。

  2. The two survivor spaces:Eden 區滿時會將仍然存活的對象複製至其中一個survivor區,當這個區又滿時將複製至另一個survivor區,以下圖:

 

 

3.2 Garbage Collectors

HotSpot 目前有三種可用的回收器: Serial GC, Parallel GCConcurrent Mark-Sweep(CMS GC),回收類型分爲mirror GCmajor GC(也叫作Full GC),mirror GC針對young generation,major GC針對 old generation。

注:查詢應用使用的垃圾回收與內存分配狀況使用:jmap -heap [PID],其中PID爲進程ID,稍候的文章會可能會有示例。

3.2.1 Serial GC

    當Java 應用啓動時若是加入-client 參數則使用這個回收器(若是不指定會根據官方的判斷方法(如CPU核數,內存大小等)來決定使用 -server仍是 -client,詳情請查詢官方資料),在目前的硬件配置來講,指定-server是大部分場景所需。

    該回收器的特色是單線程回收(在這個多核時代明顯不多被選擇),mirro GC和major GC都須要暫停應用(即stop-the-world)

,以下圖:

 

3.2.2 Parallel GC

    在parallel GC 中,minor 和 full GC 都是並行的,可以使用多核並行回收,可指定使用多少線程,能很好改善應用吞吐量,通常的機器配置中,若是不指定所使用的GC collector,通常默認爲 Parallel GC,它符合大多數應用場景。以下圖:

 

3.2.3 CMS GC

    該回收器的出現主要應對低響應延時的應用場景,即  stop-the-world 一旦發生,應用將中止響應直到它工做完成,這狀況在不少WEB、電信或銀行應用中尤其關鍵。雖然 major GC不多發生,可是一旦發生將耗時較長,特別是在Heap很大的時候(也就是咱們爲何不能讓Heap分配很大的緣由,適立即可,可有效分散GC暫停的時間使得不會感到明顯卡頓)。

    CMS GC 旨在減小應用的響應時間(但會犧牲一些吞吐量),因爲延長了GC的工做週期,因此有更大的heap區要求(在marking pause階段仍然容許分配內存),它同時具備更復雜的算法,CMS GC在管理 young generation方面與 Serial/Parallel GC同樣(經過參數指定,稍候的文章中會有詳解),在管理 old generation 採用2個週期來回收以減小應用暫停時間,以下圖:

 

大體動做描述分爲:

1. Initial mark,標記在old generation以外的全部可直接抵達的對象(如靜態對象、線程棧等)。

2. Concurrent marking phase,在這個階段標記全部可抵達的(直接或間接)存活對象,在這個階段應用是不須要中止的,標記線程和應用線程同時工做。

3. Concurrent pre-cleaning爲了減輕Remark pause步驟的工做,如訪問在making phase階段被更改的引用,雖然Remark pause仍是會作這工做,可是它能顯著地減小Remark pause的持續時間。

4. Remark pause,因爲應用工做時會不停地更新引用,因此最後仍是須要暫停應用來完全標記,這步驟從新檢查前一步驟(Concurrent marking phase)到暫停這時間段內更新的引用。

5. Concurrent sweeping phase,這步驟已經知道了全部在old generation存活的對象,將從新整理內存和釋放不可達對象(以下圖b所示),釋放後標識空閒空間,但他們是不連續的(Serial和parallel GC 則採用下圖a的壓縮方式),因此將致使在old generation分配內存代價比較昂貴。Marking pause階段能保證標記在這階段存活的對象,但不能保證全部不可達的對象能被清理,若是本次回收不能清理,則下次Full GC的時候將被回收。

 

最後談內存碎片問題:因爲缺乏內存壓縮將使得內存碎片化,CMS 提供壓縮週期,這週期也是stop the world,和serial、parallel GC 類似。使用-XX:CMSFullGCsBeforeCompaction參數指定full GC 多少次後進行內存壓縮,默認爲0,通常可設置爲1,稍候的文章中會有相關GC的參數詳解。

 

3.2.4 The Garbage-First GC

    G1 GC 將來將做爲 CMS GC 的一種替代(在Java 6 update 20 or later和 Java 7中才具備), 它可指定GC致使的應用中止時間,有不一樣的內存分佈,提升應用實時性,但該GC 未通過大量的生產環境應用驗證,目前只做爲實驗性特徵使用。

 

關於這幾個GC行爲對好比下圖:

 

4. 總結

JVM 調優主要在3點:

1. 選擇合適的 GC collector.

2. 指定合適的 heap area大小.

3. 指定young generation的比例(或大小),它影響到 Full GC發生的頻率以及應用暫停時間.

相關文章
相關標籤/搜索