本系列筆記主要基於《深刻理解Java虛擬機:JVM高級特性與最佳實踐 第2版》,是這本書的讀書筆記。java
垃圾收集算法是是內存回收的方法論,垃圾收集器是內存回收的具體實現。不一樣的虛擬機會有不一樣的垃圾收集器的實現,咱們主要討論的是默認的HotSpot虛擬機,這個虛擬機包含的垃圾收集器以下圖;算法
如上圖所示,一共有7種垃圾收集器,若是兩個垃圾收集器之間有雙箭頭連線,則兩個垃圾收集器可搭配使用。上面是新生代的收集器,下面是老年代的收集器。每一個垃圾收集器都有各自適合的使用場景。數據結構
Serial是一個「單線程」的新生代收集器,使用複製算法,它只會使用一個CPU或者一條收集器線程去完成垃圾收集工做,而且它在垃圾收集時,必須暫停全部其餘的工做線程,直到它收集結束。「Stop The World」會在用戶不可見的狀況下,把用戶的工做線程所有停掉,這每每是使人難以接受的。多線程
下圖是 Serial/Serial Old 收集器運行示意圖:併發
上圖中,新生代是Serial收集器採用複製算法,老年代是Serial Old收集器採用標記-整理算法。Serial雖然是一個缺點鮮明的收集器,但它依然是虛擬機在Client模式下的默認收集器,它也有優勢,好比簡單高效(與其餘收集器單線程相比),對於單個CPU來講,Serial因爲沒有線程交互的開銷,效率比較高,對於桌面應用來講,分配給虛擬機的內存不會很大,收集時的停頓也是在可接受範圍內的。性能
ParNew收集器是Serial收集器的多線程版本,也是使用複製算法的新生代收集器,它除了使用多條線程進行垃圾收集之外,其餘的好比收集器的控制參數、收集算法、Stop-The-World、對象分配規則、回收策略都和Serial收集器徹底同樣。線程
下圖是 ParNew/Serial Old 收集器運行示意圖:3d
上圖中,新生代是ParNew收集器採用複製算法,老年代是Serial Old收集器採用標記-整理算法。ParNew是許多Server模式下虛擬機的首選新生代收集器,可能是由於它能與CMS收集器配合工做。CMS收集器是HotSpot虛擬機中第一個併發的垃圾收集器,CMS第一次實現了讓用戶線程與垃圾收集線程同時工做。日誌
簡單介紹下垃圾收集中的並行與併發概念:code
Parallel Scavenge也是使用複製算法的新生代收集器,而且也是一個並行的多線程收集器。Parallel收集器跟其它收集器關注GC停頓時間不一樣,它關注的是吞吐量。低停頓時間適合須要與用戶交互的程序,而高吞吐量能夠高效率的利用CPU時間,能儘快完成運算任務,適合用於後臺計算較多而交互較少的任務。
Parallel收集器提供了兩個虛擬機參數用以控制吞吐量,-XX:MaxGCPauseMillis
參數能夠控制垃圾收集的最大停頓時間,-XX:GCTimeRatio
參數能夠直接設置吞吐量大小。
-XX:MaxGCPauseMillis
的值是一個大於0的毫秒數,使用它減少GC停頓時間是犧牲吞吐量和新生代空間換來的,例如系統把新生代調小,收集300M的新生代確定比500M的快,這也致使垃圾收集發生的更頻繁,原來10秒收集一次每次停頓100毫秒,如今5秒收集一次每次停頓70毫秒,停頓時間降低了,可是吞吐量也降低了。
-XX:GCTimeRatio
的值是一個0到100的整數,經過它咱們告訴JVM吞吐量要達到的目標值,-XX:GCTimeRatio=N
指定目標應用程序線程的執行時間(與總的程序執行時間)達到N/(N+1)的目標比值。例如,它的默認值是99,就是說要求應用程序線程在整個執行時間中至少99/100是活動的(GC線程佔用其他的1/100),也就是說,應用程序線程應該運行至少99%的總執行時間。
除這兩個參數外,還有一個參數-XX:-UseAdaptiveSizePolicy
值得關注,這是一個開關參數,當它打開以後,就不須要手工指定新生代大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據系統的運行狀況收集性能監控信息,動態的調整這些參數來提升GC性能,這種調節方式稱爲GC自適應調節策略。這個參數是默認激活的,自適應行爲也是JVM優點之一。
Serial Old是Serial收集器的老年代版本,一樣是一個「單線程」收集器,使用標記-整理算法。這個收集器主要是給Client模式下的虛擬機使用,Server模式下還有兩個用途,一個是在JDK1.5及以前的版本中與Parallel Scavenge收集器搭配使用,另外一個是做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。工做過程請看Serial 收集器部分的 Serial/Serial Old 收集器運行示意圖。
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多線程和標記-整理算法。此收集器在JDK1.6中開始出現,在Parallel Old出現以前,只有Serial Old可以與Parallel Scavenge收集器配合使用。因爲Serial Old這種單線程收集器的性能拖累,致使在老年代比較大的場景下,Parallel Scavenge和Serial Old的組合吞吐量甚至還不如ParNew加CMS的組合。而有了Parallel Old收集器以後,Parallel Scavenge與Parallel Old成了名副其實的吞吐量優先的組合,在注重吞吐量和CPU資源敏感的場景下,均可以優先考慮這對組合。
下圖是 ParNew/Serial Old 收集器運行示意圖:
CMS(Concurrent Mark Sweep)收集器是基於標記-清除算法的老年代收集器,它以獲取最短回收停頓時間爲目標。CMS是一款優秀的收集器,特色是併發收集、低停頓,它的運行過程稍微複雜些,分爲4個步驟:
4個步驟中只有初始標記、從新標記這兩步須要「Stop The World」。初始標記只是標記一下GC Roots能直接關聯的對象,速度很快。併發標記是進行GC Roots Tracing的過程,也就是從GC Roots開始進行可達性分析。從新標記則是爲了修正併發標記期間因用戶線程繼續運行而致使標記發生變更的那一部分記錄。併發清理固然就是進行清理被標記對象的工做。
下圖是 CMS 收集器運行示意圖:
整個過程當中,併發標記與併發清除過程耗時最長,但它們均可以與用戶線程一塊兒工做,因此總體上說,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
可是CMS收集器也並不完美,它有如下3個缺點:
G1(Garbage-First)收集器是面向服務端應用的垃圾收集器,它被寄予厚望以用來替換CMS收集器。在G1以前的收集器中,收集的範圍要麼是整個新生代要麼就是老年代,而G1再也不從物理上區分新生代老年代,G1能夠獨立管理整個Java堆。它將Java堆劃分爲多個大小相等的獨立區域(Region),雖然還有新生代老年代的概念,但再也不是物理隔離的,而都是一部分Region(不須要連續)的集合。
與其餘收集器相比,G1收集器的特色有:
G1收集器之因此能創建可預測的停頓時間模型,是由於它能夠避免在整個Java堆中進行全區域的垃圾收集,G1根據各個Region裏垃圾堆積的價值大小(回收所獲空間大小及所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region,這也是Garbage-First名稱的由來。
G1收集器的Region以下圖所示:
圖中的E表明是Eden區,S表明Survivor,O表明Old區,H表明humongous表示巨型對象(大於Region空間的對象)。從圖中能夠看出各個區域邏輯上並非連續的,而且一個Region在某一個時刻是Eden,在另外一個時刻就可能屬於老年代。G1在進行垃圾清理的時候就是將一個Region的對象拷貝到另一個Region中。
再介紹一個概念:Remembered Set(記憶集)。每一個Region中都有一個Remembered Set,記錄的是其餘Region中的對象引用本Region對象的關係(誰引用了個人對象)。因此在垃圾回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏。G1裏面還有另一種數據結構叫Collection Set,Collection Set記錄的是GC要收集的Region的集合,Collection Set裏的Region能夠是任意代的。在GC的時候,對於跨代對象引用,只要掃描對應的Collection Set中的Remembered Set便可。
不算上維護Remembered Set的話,G1收集器的收集過程以下圖所示:
如圖所示,G1收集過程有以下幾個階段:
初始標記只標記一下GC Roots能關聯到的對象,須要停頓線程可是耗時短,會停頓用戶線程(Stop the World)。併發標記是從GC Root開始對堆中對象進行可達性分析,找出存活對象,這階段耗時長可是能夠與用戶線程併發執行。最終標記就是爲了修正在併發標記階段,因用戶線程繼續運行而致使標記產生變更的那一部分標記記錄,這階段須要停頓用戶線程(Stop the World),可是可並行執行。篩選回收階段會對各個Region的回收價值和成本進行排序,根據用戶指望的GC停頓時間來制定回收計劃,該階段也是會停頓用戶線程(Stop the World)。
查詢當前使用的垃圾收集器:
java -XX:+PrintCommandLineFlags -version
此命令讓JVM打印出那些已經被用戶或者JVM設置過的詳細的XX參數的名稱和值。
VM參數 | 描述 |
---|---|
-XX:+UseSerialGC | 指定Serial收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParNewGC | 指定ParNew收集器+Serilal Old組合執行內存回收 |
-XX:+UseParallelGC | 指定Parallel收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParallelOldGC | 指定Parallel收集器+Parallel Old收集器組合執行內存回收 |
-XX:+UseConcMarkSweepGC | 指定CMS收集器+ParNew收集器+Serial Old收集器組合執行內存回收。優先使用ParNew收集器+CMS收集器的組合,當出現ConcurrentMode Fail或者Promotion Failed時,則採用ParNew收集器+Serial Old收集器的組合 |
-XX:+UseG1GC | 指定G1收集器併發、並行執行內存回收 |
-XX:+PrintGCDetails | 打印GC詳細信息 |
-XX:+PrintGCTimeStamps | 輸出GC的時間戳(以基準時間的形式) |
-XX:+PrintGCDateStamps | 輸出GC的時間戳(以日期的形式) |
-XX:+PrintHeapAtGC | 在進行GC的先後打印出堆的信息 |
-XX:+PrintTenuringDistribution | 在進行GC時打印survivor中的對象年齡分佈信息 |
-Xloggc:$CATALINA_HOME/logs/gc.log | 指定輸出路徑收集日誌到日誌文件 |
-XX:NewRatio | 新生代與老生代(new/old generation)的大小比例(Ratio). 默認值爲 2 |
-XX:SurvivorRatio | eden/survivor 空間大小的比例(Ratio). 默認值爲 8 |
-XX:GCTimeRatio | GC時間佔總時間的比率,默認值99%,僅在Parallel Scavenge收集器時生效 |
-XX:MaxGCPauseMills | 設置GC最大停頓時間,僅在Parallel Scavenge收集器時生效 |
-XX:PretensureSizeThreshold | 直接晉升到老年代的對象大小,大於這個參數的對象直接在老年代分配 |
-XX:MaxTenuringThreshold | 提高年老代的最大臨界值(tenuring threshold). 默認值爲 15 |
-XX:UseAdaptiveSizePolicy | 動態調整Java堆中各個區域的大小及進入老年代的年齡 |
-XX:HandlePromotionFailure | 是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代整個Eden和Survivor中對象都存活的極端狀況 |
-XX:ParallelGCThreads | 設置垃圾收集器在並行階段使用的線程數,默認值隨JVM運行的平臺不一樣而不一樣 |
-XX:ParallelCMSThreads | 設定CMS的線程數量 |
-XX:ConcGCThreads | 併發垃圾收集器使用的線程數量. 默認值隨JVM運行的平臺不一樣而不一樣 |
-XX:CMSInitiatingOccupancyFraction | 設置CMS收集器在老年代空間被使用多少後觸發垃圾收集,默認68% |
-XX:+UseCMSCompactAtFullCollection | 設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片的整理 |
-XX:CMSFullGCsBeforeCompaction | 設定進行多少次CMS垃圾回收後,進行一次內存壓縮 |
-XX:+CMSClassUnloadingEnabled | 容許對類元數據進行回收 |
-XX:CMSInitiatingPermOccupancyFraction | 當永久區佔用率達到這一百分比時,啓動CMS回收 |
-XX:UseCMSInitiatingOccupancyOnly | 表示只在到達閥值的時候,才進行CMS回收 |
-XX:InitiatingHeapOccupancyPercent | 指定當整個堆使用率達到多少時,觸發併發標記週期的執行,默認值是45% |
-XX:G1HeapWastePercent | 併發標記結束後,會知道有多少空間會被回收,再每次YGC和發生MixedGC以前,會檢查垃圾佔比是否達到此參數,達到了纔會發生MixedGC |
-XX:G1ReservePercent | 設置堆內存保留爲假天花板的總量,以下降提高失敗的可能性. 默認值是 10 |
-XX:G1HeapRegionSize | 使用G1時Java堆會被分爲大小統一的的區(region)。此參數能夠指定每一個heap區的大小. 默認值將根據 heap size 算出最優解. 最小值爲 1Mb, 最大值爲 32Mb |