一、簡介算法
對於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 GC 和 Concurrent Mark-Sweep(CMS GC),回收類型分爲mirror GC 和 major 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發生的頻率以及應用暫停時間.