JVM學習筆記——垃圾收集器與內存分配策略(2)

垃圾收集器

java虛擬機規範中並沒有對垃圾收集器如何實現有任何規定,因此,不同的廠商,不同版本的虛擬機所提供的垃圾收集器可能會有很大差別,這裏只討論基於JDK1.7之後的HotSpot虛擬機。這個虛擬機包括的收集器如下圖所示:
這裏寫圖片描述
上圖展示了7種不同的垃圾收集器,如果兩個垃圾收集器中存在連線,就證明這兩種垃圾收集器可以搭配使用。它們所處的區域,代表了他們是在新生代還是老年代中使用。

Serial收集器

serial的意思是單線程,這不僅僅意味着它會使用一個cpu或者一條工作線程去完成垃圾收集,更重要的是它在進行垃圾收集時,必須暫停所有其他工作線程,俗稱“stop the world”,雖然聽起來很酷,但是如果想象以下你每用一小時軟件就要卡上個5分鐘進行垃圾收集,這並不是什麼很好的體驗。
這裏寫圖片描述
(圖畫的不好請見諒)
雖然虛擬機的開發團隊一直在爲了減少因爲內存回收而導致的停頓時間,隨着一個個越來越優秀的收集器的出現,用戶線程的停頓時間在不斷縮短,但是無法完全消除。雖然serial收集器看起來雞肋,但是到現在爲止,它仍然是虛擬機運行在客戶端情況下的默認收集器。它簡單而高效,對單線程的垃圾收集效率極高,而對用戶的桌面應用場景下,新生代一般也就100~200mb的樣子,這樣的垃圾回收導致的停頓可以控制在100ms內,只要不是頻繁的發生,這是完全可以接受的。

ParNew收集器

這其實就是serial收集器的多線程版本,除了使用多線程進行垃圾收集之外,其餘行爲包括serial收集器可用的所有控制參數,收集算法,stop the world,對象分配原則,回收策略等都與serial收集器完全一樣,兩者公用了相當多的代碼。
這裏寫圖片描述
ParNew收集器是在新生代唯一可以配合CMS收集器的,ParNew收集器在單cpu的環境中絕對不會有比serial收集器更好的效果,甚至因爲存在線程交互的開銷,該收集器在通過超線程實現的雙核環境下都不能保證一定超過serial收集。當然,隨着cpu的數量的增加,它對於提升GC時系統資源的利用率還是有好處的。
tips:
並行:多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態;
併發:指用戶線程和垃圾收集線程同時執行(但不一定是並行的,可能是交替執行),用戶程序在繼續運行,而垃圾收集器運行與另一個CPU上。

Parallel Scavenge收集器

這也是一個新生代收集,使用複製算法,也是並行的多線程收集器,雖然聽起來和前者一樣,但是關注點和其他的收集器不同。CMS等其他收集器的目的是儘可能的縮短垃圾收集時用戶線程的停頓時間,而此收集器的目的是達到一個可控的吞吐量(throughput),又被稱爲吞吐量優先收集器。吞吐量=運行用戶代碼時間/(運行用戶代碼事件+垃圾收集事件)。
停頓時間越短越有利於和用戶交互的程序,良好的反應速度可以提升用戶體驗。而高吞吐量則可以高效率的利用cpu時間,儘快完成程序的運算任務,適合在後臺不需要太多交互的任務。
該收集器提供了兩個參數用於精確控制吞吐量,分別控制最大垃圾收集停頓時間的-XX:MaxGCPauseMills參數以及直接設置吞吐量大小的-XX:GCTimeRadio參數。
前者可以設置爲一個大於0的毫秒數,收集器將儘可能保證垃圾收集事件不超過設定值。不過不要一味調低這個值,因爲垃圾收集變得更快是以犧牲吞吐量和新生代空間換取的,把新生代調小一點,比如從500mb變爲300mb,垃圾收集事件從每10s一次,每次停頓100ms變成現在每5s一次,每次70ms,雖然垃圾回收事件減少了,但是吞吐量也降下來了。
後者代表的是程序運行時間的比例,可以設置爲1~100之間的整數,比如設置爲19,那麼垃圾收集時間就佔1/(1+19) = 5%,如果設置爲99,那麼垃圾收集時間就佔1/(99+1)= 1%。

Serial Old收集器

serial收集器的老年代版本,使用標記-整理算法,這個收集器的主要意義在於給client模式下的虛擬機使用,如果在server環境下,主要用於在JDK1.5以及之前的版本與Parallel scavenge收集器搭配使用,另一用途就是作爲CMS收集器的後備方案。
這裏寫圖片描述

Parallel Old收集器

Parallel scavenge收集器的老年代版本,使用多線程與標記-整理算法。這個收集器出現的最大作用就是可以與Parallel scavenge搭配使用,解決了原來只可以使用serial old收集器的尷尬,因爲serial old收集器在老年代無法充分發揮服務器多cpu的處理能力。在吞吐量優先的環境中,可以考慮使用parallel old + Parallel scavenge 的組合。
這裏寫圖片描述

CMS收集器

CMS收集器是一種以獲取最短回收停頓時間的收集器,主要用於javaweb中的服務端上。它基於標記-清除算法,它的運作過程相對複雜,分爲:

  • 初始標記
  • 併發標記
  • 重新標記
  • 併發清除

其中,初始標記和重新標記仍需要“stop the world”。初始標記僅僅是標記以下GC roots能到達的對象,速度很快;併發標記是進行GC root tracing的過程,而重新標記階段是爲了修正因爲用戶程序繼續運行導致標記變動的那一部分對象的標記記錄,這個時間一般比初始標記的時間長,但是遠比並發標記的時間短。
由於整個過程中耗時最長的併發標記與併發清除收集器線程可以與用戶線程一起工作,所以,總體上來說,CMS的內存回收過程是和用戶線程一起併發進行的。
這裏寫圖片描述
CMS是一款優秀的收集器,被稱爲併發多線程收集器,但是,它也存在3個明顯的缺點:
- CMS收集器對收集器資源非常敏感,CMS默認啓動的回收線程數是(cpu數量+3)/4,很明顯,cpu越少,收集器線程佔用的線程越多,吞吐量變低,應用程序變慢。
- CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗導致另一次full gc的發生。
CMS在併發清理階段用戶線程還在運行着,伴隨運行着自然會有垃圾產生,這部分垃圾在產生後,CMS無法在當次垃圾回收中清理它們,只能等待下一次垃圾回收,這被稱爲“Floating Garbage”。同時,由於垃圾回收階段用戶線程仍然在運行,故CMS收集器不能等到老年代被填滿之後才進行收集,需要預留一部分空間提供併發收集的程序運作使用。如果CMS運行期間預留的內存無法滿足程序需要,就會出現一次“Concurrent Mode Failure”,這是虛擬機將啓動後備預案,臨時啓動serial old收集器進行老年代的收集。
- 最後一個缺點,CMS收集器是一個基於標記-清除算法的收集器,這種算法會導致大量內存碎片的產生,會給大對象的分配造成麻煩,往往老年代還有很多空間,但是無法找到足夠大的連續空間。可以採取-XX:+UseCMSCompactAtFullCollection開關參數,在收集器頂不住的時候進行full gc時候進行內存的合併整理,但是耗時邊長。

G1收集器

G1是一個面向服務端應用的垃圾回收器,目標是替換CMS收集器,有以下特點:
- 並行與併發:充分利用多核,多cpu的性能優勢,縮短“stop the word”的運行時間。
- 分代收集:雖然可以不用其他收集器配合就可以獨立管理整個java堆,但是採取不同的方式對待新創建的對象和創建已久,熬過多次垃圾收集的舊對象。
- 空間整合:從整體來看基於標記-整理算法,從局部來看基於複製算法,運行時不會產生內存碎片,收集能提供規整的可用內存。
- 可預測的停頓:G1除了追求低停頓外,還建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的的時間片段內,這幾乎是實時垃圾收集器的特徵了。
G1收集器將整個java堆劃分爲多個大小相等的獨立區域(region),雖然還保留這新生代和老年代的概念,但新生代和來年代不再是物理隔離的,都是一部分region的集合(不需要連續)。G1收集器跟蹤每個region裏面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次更具允許的收集時間,優先回收價值最大的region。
G1收集器的運作大致可以分爲以下幾個步驟:
1. 初始標記
2. 併發標記
3. 最終標記
4. 篩選回收
這裏寫圖片描述

內存分配與回收策略

對象的內存分配,往大方向講,就是在堆上分配(也有可能是在經過JIT編譯後拆散爲標量類型間接的在棧上分配),對象主要分配在新生代的Eden區上,如果啓動了本地線程分配緩衝(Thread Local Allocation Buffer),線程優先在TLAB上分配,少數情況下直接分配在老年代上,分配規則並不是完全確定的,是具體情況而定。

對象優先在Eden上分配

大多數情況下,對象在新生代eden區分配,當eden區沒有足夠空間進行分配時,虛擬機進行一次Minor GC。

Minor GC和Full GC有什麼不同?
Minor GC:發生在新生代的垃圾回收動作,因爲大多數java對象都有存活時間短的特性,所以Minor GC分成頻繁,回收速度塊。
Full GC:發生在老年代的GC,出現了Full GC,經常伴隨着至少一次Minor GC,Full GC的速度通常比前者慢十倍以上。

大對象直接進入老年代

大對象指需要大量連續內存的java對象,典型的大對象是那種很長的字符串與數組,這對於內存分配是一個壞消息,更壞的消息就是遇到一羣短命的大對象,編程時儘量避免。大對象容易因爲無法安置提前觸發 GC。
虛擬機提供了一個-XX:PretenureSizeThreshold參數,大於這個值的對象直接進入老年代分配,避免在eden區發生大量的內存複製操作。

長期存活的對象進入老年代

爲了識別哪些對象應放在新生代,哪些在老年代,虛擬機給每個對象定義了一個Age計數器,如果對象在eden出生並且經過一次MinorGC仍然存活並且可以被Survivor容納的話,將被移動到Survivor空間中,並且對象年齡設爲1,對象每熬過一次MinorGC,年齡增加一歲,當age大於某個值,默認爲15時,晉升到老年代中。

動態判斷對象年齡

如果在Survivor空間中的想通年齡的多有對象大小總和大於Survivor空間的一半,年齡大於等於這個值的對象直接升入老年代,無需滿足上一條的要求。

空間分配擔保

這裏寫圖片描述

發佈了40 篇原創文章 · 獲贊 10 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章