(七)Java垃圾收集器详解
面試官問:Java垃圾收集器了解過多少,說一下 JVM 有哪些垃圾回收器?這些問題在你面試高級Java的時候經常會問到。本篇文章結合著【深入理解Java虛擬機】一書當中整理了本篇博客。
如果想要對收集器了解的更深,建議一點一點讀,如果想大概了解一下,只是為了面試問到可以簡單說一下,那么可以直接看我下方寫的總結,總結相對來說內容不是很多,但是每個收集器優缺點都整理到了。
目錄
- 一、概述
- 二、Java垃圾收集器
- 2.1、Serial收集器
- 2.2、ParNew收集器
- 2.3、Parallel Scavenge收集器
- 2.4、Serial Old收集器
- 2.5、Parallel Old收集器
- 2.6、CMS收集器
- 2.7、Garbage First收集器
- 三、總結
- 3.1、JVM默認用的哪個收集器?
- 3.2、Serial收集器
- 3.3、ParNew收集器
- 3.4、Parallel Scavenge收集器
- 3.5、Serial Old收集器
- 3.6、Parallel Old收集器
- 3.7、CMS收集器
- 3.8、G1收集器
JVM的知識是連貫性的,如果你連Java內存分布,以及垃圾回收相關知識都不知道,建議您不要讀這篇文章,讀起來會讓你失去學習的興致。可以先去讀讀我前面所寫的JVM相關知識。
一、概述
如果說收集算法是內存回收的方法論,那垃圾收集器就是內存回收的實踐者。
Java垃圾收集器這個不學就不能工作嗎?答案是不是,這個代表你掌握JVM的深度。Java離開JVM就不行嗎,答案是肯定的,這個問就好似程序員離開電腦能行嗎,之前我是深深體會到JVM不學好,會吃多大的虧。項目內存出現瓶頸問題根本無從下手,這都是需要依靠這些知識的積累,才能全方面的去解決JVM優化問題。
我們都知道Java默認的虛擬機類型是HotSpot虛擬機,HotSpot虛擬機有多種不同的收集器,既然實現這么多種,意味著每一種都有每一種的作用。針對這一點,我們進行深入學習Java垃圾收集器。
垃圾收集器是干什么的?為什么要了解他呢?
很多人其實對這塊根本不理解,只知道面試的時候會經常問垃圾回收算法,而且還知道有三種,標記清除法、標記復制法、標記整理法,如果再問你,虛擬機用的哪個算法你還知道嗎?答案是,三種都用到了。
各款經典收集器之間的關系 如圖所示。
七種作用于不同分代的收集器,如果兩個收集器之間存在連線,就說明它們可以搭配 使用,圖中收集器所處的區域,則表示它是屬于新生代收集器抑或是老年代收集器。接下來筆者將 逐一介紹這些收集器的目標、特性、原理和使用場景,并重點分析CMS和G1這兩款相對復雜而又廣泛 使用的收集器,深入了解它們的部分運作細節。
雖然垃圾收集器的技術在不斷進步,但直到現在還沒有 最好的收集器出現,更加不存在“萬能”的收集器,所以我們選擇的只是對具體應用最合適的收集器。
二、Java垃圾收集器
2.1、Serial收集器
Serial收集器是最基礎、歷史最悠久的收集器,曾經(在JDK 1.3.1之前)是HotSpot虛擬機新生代 收集器的唯一選擇。大家只看名字就能夠猜到,這個收集器是一個單線程工作的收集器,但它的“單線 程”的意義并不僅僅是說明它只會使用一個處理器或一條收集線程去完成垃圾收集工作,更重要的是強 調在它進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。“Stop The World”這個詞語也 許聽起來很酷,但這項工作是由虛擬機在后臺自動發起和自動完成的,在用戶不可知、不可控的情況 下把用戶的正常工作的線程全部停掉,這對很多應用來說都是不能接受的。讀者不妨試想一下,要是 你的電腦每運行一個小時就會暫停響應五分鐘,你會有什么樣的心情?下圖示意了Serial/Serial Old收 集器的運行過程。
為什么要停止所有用戶線程?
對于“Stop The World”帶給用戶的惡劣體驗,早期HotSpot虛擬機的設計者們表示完全理解,但也 同時表示非常委屈:因為你要進行清理的時候,你正清理著,還有用戶線程在造著,這清理到什么時候是個頭。
從JDK 1.3開始,一直到現在最新的JDK 13,HotSpot虛擬機開發團隊為消除或者降低用戶線程因 垃圾收集而導致停頓的努力一直持續進行著,從Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最終至現在垃圾收集器的最前沿成果Shenandoah和ZGC 等,我們看到了一個個越來越構思精巧,越來越優秀,也越來越復雜的垃圾收集器不斷涌現,用戶線 程的停頓時間在持續縮短。
迄今為止,Serial收集器依然是HotSpot虛擬機運行在客戶端模式下的默認新生 代收集器。
什么是Java客戶端?
客戶端就是指的的桌面可以直接點擊的應用,有的會直接在客戶端里面集成JDK,用戶便可以點擊直接運行,當關閉應用的時候,就相當于JVM停止,點擊應用的時候,JVM開始運行,所以一般客戶端的JVM我們要的是單線程性價比最高的。
優點:
在用戶桌面的應用場景以及近年來流行的部分微服務應用中,分配給虛 擬機管理的內存一般來說并不會特別大,收集幾十兆甚至一兩百兆的新生代,垃圾收集的停頓時間完全可以控制在十幾、幾十毫秒,最多一 百多毫秒以內,只要不是頻繁發生收集,這點停頓時間對許多用戶來說是完全可以接受的。所以,Serial收集器對于運行在客戶端模式下的虛擬機來說是一個很好的選擇。
2.2、ParNew收集器
ParNew收集器實質上是Serial收集器的多線程并行版本,除了同時使用多條線程進行垃圾收集之 外,其余的行為包括Serial收集器可用的所有控制參數(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對象分配規 則、回收策略等都與Serial收集器完全一致,在實現上這兩種收集器也共用了相當多的代碼。ParNew收 集器的工作過程如圖所示。
ParNew在JDK7之前是服務端模式下的HotSpot虛擬機 首選的新生代收集 器,原因:除了Serial收集器外,目前只有它能與CMS 收集器配合工作。
在JDK 5發布時,HotSpot推出了一款在強交互應用中幾乎可稱為具有劃時代意義的垃圾收集器 ——CMS收集器。這款收集器是HotSpot虛擬機中第一款真正意義上支持并發的垃圾收集器,它首次 實現了讓垃圾收集線程與用戶線程(基本上)同時工作。
遺憾的是,CMS作為老年代的收集器,卻無法與JDK 1.4.0中已經存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 5中使用CMS來收集老年代的時候,新生代只能選擇ParNew或者 Serial收集器中的一個。ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC選項)的默 認新生代收集器,也可以使用-XX:+/-UseParNewGC選項來強制指定或者禁用它。
可以說直到CMS的出現才鞏固了ParNew的地位,但成也蕭何敗也蕭何,隨著垃圾收集器技術的不 斷改進,更先進的G1收集器帶著CMS繼承者和替代者的光環登場。G1是一個面向全堆的收集器,不 再需要其他新生代收集器的配合工作。所以自JDK 9開始,ParNew加CMS收集器的組合就不再是官方 推薦的服務端模式下的收集器解決方案了。官方希望它能完全被G1所取代,甚至還取消了ParNew加 Serial Old以及Serial加CMS這兩組收集器組合的支持(其實原本也很少人這樣使用),并直接取消了- XX:+UseParNewGC參數,這意味著ParNew和CMS從此只能互相搭配使用,再也沒有其他收集器能 夠和它們配合了。讀者也可以理解為從此以后,ParNew合并入CMS,成為它專門處理新生代的組成部 分。ParNew可以說是HotSpot虛擬機中第一款退出歷史舞臺的垃圾收集器。
ParNew收集器在單核處理器的環境中絕對不會有比Serial收集器更好的效果,甚至由于存在線程 交互的開銷,該收集器在通過超線程(Hyper-Threading)技術實現的偽雙核處理器環境中都不能百分之百保證超越Serial收集器。當然,隨著可以被使用的處理器核心數量的增加,ParNew對于垃圾收集時 系統資源的高效利用還是很有好處的。它默認開啟的收集線程數與處理器核心數量相同,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。
注意 從ParNew收集器開始,后面還將會接觸到若干款涉及“并發”和“并行”概念的收集器。 在大家可能產生疑惑之前,有必要先解釋清楚這兩個名詞。并行和并發都是并發編程中的專業名詞, 在談論垃圾收集器的上下文語境中,它們可以理解為:
-
并行(Parallel):并行描述的是多條垃圾收集器線程之間的關系,說明同一時間有多條這樣的線 程在協同工作,通常默認此時用戶線程是處于等待狀態。
-
并發(Concurrent):并發描述的是垃圾收集器線程與用戶線程之間的關系,說明同一時間垃圾 收集器線程與用戶線程都在運行。由于用戶線程并未被凍結,所以程序仍然能響應服務請求,但由于 垃圾收集器線程占用了一部分系統資源,此時應用程序的處理的吞吐量將受到一定影響。
Parallel Scavenge收集器及 后面提到的G1收集器等都沒有使用HotSpot中原本設計的垃圾收集器的分代框架,而選擇另外獨立實 現。Serial、ParNew收集器則共用了這部分的框架代碼,
詳細可參考: https://blogs.oracle.com/jonthecollector/our_collectors。
2.3、Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代收集器,它同樣是基于標記-復制算法實現的收集器,也是 能夠并行收集的多線程收集器……Parallel Scavenge的諸多特性從表面上看和ParNew非常相似,那它有 什么特別之處呢?
Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能 地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐 量(Throughput)。所謂吞吐量就是處理器用于運行用戶代碼的時間與處理器總消耗時間的比值, 即
可以的當做是一個輸入和輸出的比值。
如果虛擬機完成某個任務,用戶代碼加上垃圾收集總共耗費了100分鐘,其中垃圾收集花掉1分 鐘,那吞吐量就是99%。停頓時間越短就越適合需要與用戶交互或需要保證服務響應質量的程序,良 好的響應速度能提升用戶體驗;而高吞吐量則可以最高效率地利用處理器資源,盡快完成程序的運算 任務,主要適合在后臺運算而不需要太多交互的分析任務。
Parallel Scavenge收集器提供了兩個參數用于精確控制吞吐量:
- -XX:MaxGCPauseMillis :控制最大垃圾收集停頓時間
- -XX:GCTimeRatio :設置吞吐量大小
- -XX:+UseAdaptiveSizePolicy :這是一 個開關參數,當這個參數被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden與Survivor區 的比例(-XX:SurvivorRatio)、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細節參數 了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時 間或者最大的吞吐量。這種調節方式稱為垃圾收集的自適應的調節策略(GC Ergonomics)。
-XX:MaxGCPauseMillis參數允許的值是一個大于0的毫秒數,收集器將盡力保證內存回收花費的 時間不超過用戶設定值。不過大家不要異想天開地認為如果把這個參數的值設置得更小一點就能使得 系統的垃圾收集速度變得更快,垃圾收集停頓時間縮短是以犧牲吞吐量和新生代空間為代價換取的: 系統把新生代調得小一些,收集300MB新生代肯定比收集500MB快,但這也直接導致垃圾收集發生得 更頻繁,原來10秒收集一次、每次停頓100毫秒,現在變成5秒收集一次、每次停頓70毫秒。停頓時間 的確在下降,但吞吐量也降下來了。
-XX:GCTimeRatio參數的值則應當是一個大于0小于100的整數,也就是垃圾收集時間占總時間的 比率,相當于吞吐量的倒數。譬如把此參數設置為19,那允許的最大垃圾收集時間就占總時間的5% (即1/(1+19)),默認值為99,即允許最大1%(即1/(1+99))的垃圾收集時間。
優點:
對于收集器運作不太了解,手工優化存在困難的話,使用Parallel Scavenge收集器配合自適應調節策 略,把內存管理的調優任務交給虛擬機去完成也許是一個很不錯的選擇。只需要把基本的內存數據設 置好(如-Xmx設置最大堆),然后使用-XX:MaxGCPauseMillis參數(更關注最大停頓時間)或- XX:GCTimeRatio(更關注吞吐量)參數給虛擬機設立一個優化目標,那具體細節參數的調節工作就 由虛擬機完成了。自適應調節策略也是Parallel Scavenge收集器區別于ParNew收集器的一個重要特性。
官方介紹:http://download.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html。
2.4、Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用標記-整理算法。這個收 集器的主要意義也是供客戶端模式下的HotSpot虛擬機使用。如果在服務端模式下,它也可能有兩種用 途:
- JDK 5以及之前的版本中與Parallel Scavenge新生代收集器搭配使用
- 作為CMS 收集器發生失敗時的后備預案,在并發收集發生Concurrent Mode Failure時使用。這兩點都將在后面的 內容中繼續講解。Serial Old收集器的工作過程如圖所示。
需要說明一下,Parallel Scavenge新生代收集器架構中本身有PS MarkSweep收集器來進行老年代收集,并非 直接調用Serial Old收集器,但是這個PS MarkSweep收集器與Serial Old的實現幾乎是一樣的,所以在官 方的許多資料中都是直接以Serial Old代替PS MarkSweep進行講解,這里筆者也采用這種方式。
2.5、Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發收集,基于標記-整理算法實 現。這個收集器是直到JDK 6時才開始提供的。
在此之前,新生代的Parallel Scavenge收集器一直處于相 當尷尬的狀態,原因是如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外別無選擇,其他表現良好的老年代收集器,如CMS無法與它配合工作。由于 老年代Serial Old收集器在服務端應用性能上的“拖累”,使用Parallel Scavenge收集器也未必能在整體上 獲得吞吐量最大化的效果。
同樣,由于單線程的老年代收集中無法充分利用服務器多處理器的并行處 理能力,在老年代內存空間很大而且硬件規格比較高級的運行環境中,Parallel Scavenge新生代+老年代Serial Old這種組合的總吞吐量甚至不一 定比ParNew加CMS的組合來得優秀。
直到Parallel Old收集器出現后,“吞吐量優先”收集器終于有了比較名副其實的搭配組合,在注重 吞吐量或者處理器資源較為稀缺的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器這個組 合。Parallel Old收集器的工作過程如圖所示。
2.6、CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很 大一部分的Java應用集中在互聯網網站或者基于瀏覽器的B/S系統的服務端上,這類應用通常都會較為 關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非 常符合這類應用的需求。
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于標記-清除算法實現的,它的運作 過程相對于前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。`;
通過下圖可以比較清楚地看到CMS收集器的運作步驟中并發和需要停頓的階段。
CMS是一款優秀的收集器,它最主要的優點在名字上已經體現出來:并發收集、低停頓,一些官 方公開文檔里面也稱之為“并發低停頓收集器”(Concurrent Low Pause Collector)。CMS收集器是 HotSpot虛擬機追求低停頓的第一次成功嘗試,但是它還遠達不到完美的程度,至少有以下三個明顯的 缺點:
缺點一:CMS收集器,雖然不會導致用戶線程停頓,但卻會因為占用了一部分線程(或者說處理器的計 算能力)而導致應用程序變慢,降低總吞吐量。CMS默認啟動的回收線程數是(處理器核心數量 +3)/4,也就是只占用不超過25%的 處理器運算資源。處理器核心數量不足四個時, CMS對用戶程序的影響就可能變得很大。就可能導致用戶程序的執行速度忽然大幅降低。
為了緩解這種情況,虛擬機提 供了一種稱為“增量式并發收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器變種,他是在并發標記、清理的時候讓收集器線程、用戶線程交替運行,盡量減少垃圾收集線程的獨占資源的 時間,這樣整個垃圾收集的過程會更長,但對用戶程序的影響就會顯得較少一些。這就好比長痛不如短痛,交替執行,用戶訪問會長時間一直卡頓,盡可能不會說直接卡死,但是資源有限,訪問會很慢。從 JDK 7開始,i-CMS模式已經被聲明不再提倡用戶使用。
缺點二:CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現“Con-current Mode Failure”失敗進而導致另一次完全“Stop The World”的Full GC的產生。
什么是浮動垃圾?
在CMS的并發標記和并發清理階 段,用戶線程是還在繼續運行的,程序在運行自然就還會伴隨有新的垃圾對象不斷產生,但這一部分 垃圾對象是出現在標記過程結束以后,CMS無法在當次收集中處理掉它們,只好留待下一次垃圾收集 時再清理掉。這一部分垃圾就稱為“浮動垃圾。
浮動垃圾帶來的危害?
同樣也是由于在垃圾收集階段用戶線程還需要持續運 行,那就還需要預留足夠內存空間提供給用戶線程使用,因此CMS收集器不能像其他收集器那樣等待 到老年代幾乎完全被填滿了再進行收集。
在JDK 5的默認設置下,CMS收集器當老年代使用了68%的空間后就會被激活,這是一個偏保守的設置,如果 在實際應用中老年代增長并不是太快,可以適當調高參數-XX:CMSInitiatingOccu-pancyFraction的值 來提高CMS的觸發百分比,降低內存回收頻率,獲取更好的性能。
到了JDK 6時,CMS收集器的啟動 閾值就已經默認提升至92%。但這又會更容易面臨另一種風險:要是CMS運行期間預留的內存無法滿 足程序分配新對象的需要,就會出現一次“并發失敗”(Concurrent Mode Failure),這時候虛擬機將不 得不啟動后備預案:凍結用戶線程的執行,臨時啟用Serial Old收集器來重新進行老年代的垃圾收集, 但這樣停頓時間就很長了。所以參數-XX:CMSInitiatingOccupancyFraction設置得太高將會很容易導致 大量的并發失敗產生,性能反而降低,用戶應在生產環境中根據實際應用情況來權衡設置。
缺點三:CMS是一款基于“標記-清除”算法實現的收集器,這意味著收集結束時會有大量空間碎片產生。空間 碎片過多時,將會給大對象分配帶來很大麻煩,往往會出現老年代還有很多剩余空間,但就是無法找 到足夠大的連續空間來分配當前對象,而不得不提前觸發一次Full GC的情況。
為了解決這個問題, CMS收集器提供了一個-XX:+UseCMS-CompactAtFullCollection開關參數(默認是開啟的,此參數從 JDK 9開始廢棄),用于在CMS收集器不得不進行Full GC時開啟內存碎片的合并整理過程,由于這個 內存整理必須移動存活對象,(在Shenandoah和ZGC出現前)是無法并發的。這樣空間碎片問題是解 決了,但停頓時間又會變長.
因此虛擬機設計者們還提供了另外一個參數-XX:CMSFullGCsBefore- Compaction(此參數從JDK 9開始廢棄),這個參數的作用是要求CMS收集器在執行過若干次(數量 由參數值決定)不整理空間的Full GC之后,下一次進入Full GC前會先進行碎片整理(默認值為0,表 示每次進入Full GC時都進行碎片整理)。
2.7、Garbage First收集器
Garbage First(簡稱G1)收集器被Oracle官方稱為“全功能的垃圾收集器”。
JDK 9發布之 日,G1宣告取代Parallel Scavenge加Parallel Old組合,成為服務端模式下的默認垃圾收集器,而CMS則 淪落至被聲明為不推薦使用(Deprecate)的收集器。
在G1收集器出現之前的所有 其他收集器,包括CMS在內,垃圾收集的目標范圍要么是整個新生代(Minor GC),要么就是整個老 年代(Major GC),再要么就是整個Java堆(Full GC)。
G1不再堅持固定大小以及固定數量的 分代區域劃分,而是把連續的Java堆劃分為多個大小相等的獨立區域(Region),Region中還有一類特殊的Humongous區域,專門用來存儲大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設 定。對于那些超過了整個Region容量的超級大對象, 將會被存放在N個連續的Humongous Region之中,G1的大多數行為都把Humongous Region作為老年代 的一部分來進行看待,如下圖所示。
用戶設定允許的收集停頓時間(使用參數-XX:MaxGCPauseMillis指定,默 認值是200毫秒)既然有這個參數那說明他有控制停頓時間的功能,他是怎么控制的?
因為它將Region作 為單次回收的最小單元,G1收集器去跟蹤各個Region里面的垃 圾堆積的“價值”大小 (價值即回收所獲得的空間大小以及回收所需時間的經驗值),然后在后臺維護一 個優先級列表,根據用戶設置的時間,優先處理回收價值收益最大的那些Region,這也就是“Garbage First”名字的由來。
這里可以這么理解:他把內存分成了多個小塊,每個塊占用的內存給記錄起來了,根據用戶設置的可停頓大小來決定回收塊的大小。
將Java堆分成多個獨立Region后,Region里面存在的跨Region引用對象如何解決?
使用記憶集避免全堆作為GC Roots掃描,每個Region都維護有自己的記憶集,使用到了雙向卡表(卡表是“我指向誰”,這種結構還記錄了“誰指向我”)。Region數量比傳統收集器的分代數量明顯要多得多,故此G1至少要耗費大約相當于Java堆容量10%至20%的額 外內存來維持收集器工作。
在并發標記階段如何保證收集線程與用戶線程互不干擾地運行?
這里首先要解決的是用戶線程改變對象引用關系時,必須保證其不能打破原本的對象圖結構,導致標記結果出現錯誤,CMS收集器采用增量更新算法實現,而G1 收集器則是通過原始快照(SATB)算法來實現的。G1把Region中的一部分空間劃分出來用于并發回收過 程中的新對象分配,這部分空間不納入回收范圍。與CMS中類似,如果內存回收的速度趕不上內存分配的速度, G1收集器也要被迫凍結用戶線程執行,導致Full GC而產生長時間“Stop The World”。
如果我們不去計算用戶線程運行過程中的動作(如使用寫屏障維護記憶集的操作),G1收集器的 運作過程大致可劃分為以下四個步驟:
-
初始標記(Initial Marking):僅僅只是標記一下GC Roots能直接關聯到的對象,并且修改TAMS 指針的值,讓下一階段用戶線程并發運行時,能正確地在可用的Region中分配新對象。這個階段需要 停頓線程,但耗時很短,而且是借用進行Minor GC的時候同步完成的,所以G1收集器在這個階段實際 并沒有額外的停頓。
-
并發標記(Concurrent Marking):從GC Root開始對堆中對象進行可達性分析,遞歸掃描整個堆 里的對象圖,找出要回收的對象,這階段耗時較長,但可與用戶程序并發執行。當對象圖掃描完成以 后,還要重新處理SATB記錄下的在并發時有引用變動的對象。
-
最終標記(Final Marking):對用戶線程做另一個短暫的暫停,用于處理并發階段結束后仍遺留 下來的最后那少量的SATB記錄。
-
篩選回收(Live Data Counting and Evacuation):負責更新Region的統計數據,對各個Region的回 收價值和成本進行排序,根據用戶所期望的停頓時間來制定回收計劃,可以自由選擇任意多個Region 構成回收集,然后把決定回收的那一部分Region的存活對象復制到空的Region中,再清理掉整個舊 Region的全部空間。這里的操作涉及存活對象的移動,是必須暫停用戶線程,由多條收集器線程并行 完成的。
從上述階段的描述可以看出,G1收集器除了并發標記外,其余階段也是要完全暫停用戶線程的, 換言之,它并非純粹地追求低延遲,官方給它設定的目標是在延遲可控的情況下獲得盡可能高的吞吐 量,所以才能擔當起“全功能收集器”的重任與期望。
毫無疑問,可以由用戶指定期望的停頓時間是G1收集器很強大的一個功能,設置不同的期望停頓 時間,可使得G1在不同應用場景中取得關注吞吐量和關注延遲之間的最佳平衡。它默認的停頓目標為兩百毫秒,如果我們把停頓時間調得非常低,譬如設置為二十毫秒,很可能出現的結 果就是由于停頓目標時間太短,導致每次選出來的回收集只占堆內存很小的一部分,收集器收集的速 度逐漸跟不上分配器分配的速度,導致垃圾慢慢堆積。所以通常 把期望停頓時間設置為一兩百毫秒或者兩三百毫秒會是比較合理的。
從G1開始,不追求一次把整個Java堆全部清理干凈。只要收集的速度能跟得上對象分配的速度,那一切就能運作得很完美。
G1和CMS區別:
目前在小內存應用上CMS的表現大概率仍然要會優于G1,而在大內存應用上G1則大多能發揮其 優勢,這個優劣勢的Java堆容量平衡點通常在6GB至8GB之間,當然,以上這些也僅是經驗之談,不 同應用需要量體裁衣地實際測試才能得出最合適的結論,隨著HotSpot的開發者對G1的不斷優化,也 會讓對比結果繼續向G1傾斜。
三、總結
Java垃圾收集器也是在不斷的進化當中,從第一版Serial單線程收集器,再到ParNew收集器,再到吞吐量優先Parallel Scavenge收集器,再到以獲取最短回收停頓時間為目標的CMS收集器,再到G1全功能的垃圾收集器。其實很多情況下,我們對收集器是無感知的。甚至很少一部分才會去真正了解收集器,甚至只知道垃圾回收是JVM做的,其他不關我的事。
我個人感覺收集器就好比手機,雖然我們人人在用,但是根本沒人關心他到底是怎么做到的。不說別的,在了解完收集器之后,最起碼能讓你對JVM的了解更上一個檔次,在面試的時候也會讓自己游刃有余。
3.1、JVM默認用的哪個收集器?
查看當前JVM的垃圾收集器
cmd中輸入以下命令:
JDK1.7 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
JDK1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
JDK1.9 默認垃圾收集器G1
在jdk8可以切換收集器嗎?
可以,通過-XX:+UseG1GC便可以切換G1收集器。但是一般情況下不要切換,JDK8默認用了Parallel收集器,那他一定有他的原因,不管是版本兼容還是什么問題,總之肯定有他的原因,所以一般情況下不建議自己更改收集器,采用默認的即可。真正需要優化了,可以選擇更改Parallel收集器的參數,例如新生代老年代大小等等。
3.2、Serial收集器
特點:
優點:
- 占用內存少,適合用在客戶端,客戶端一般是單線程并且占用內存少,而Serial收集器完全可以滿足。
缺點:
- 進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束
3.3、ParNew收集器
特點:
優點:
- 能與CMS 收集器配合工作
缺點:
3.4、Parallel Scavenge收集器
特點:
優點:
- 提供了兩個參數可以控制停頓時間還有吞吐量大小,相比用其他的收集器,假如不懂收集器機制調優很困難,而Parallel Scavenge收集器,我們只需要設置這兩個參數再加上堆大小,其他的交給虛擬機來控制即可。
缺點:
- 控制停頓時間,就是將新生代調小,反而會導致頻繁GC
可控制參數:
- -XX:MaxGCPauseMillis :控制最大垃圾收集停頓時間
- -XX:GCTimeRatio :設置吞吐量大小
- -XX:+UseAdaptiveSizePolicy :這是一 個開關參數,當這個參數被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden與Survivor區 的比例(-XX:SurvivorRatio)、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細節參數 了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時 間或者最大的吞吐量。
3.5、Serial Old收集器
3.6、Parallel Old收集器
3.7、CMS收集器
特點:
優點:通過分步驟來降低停頓時間
缺點:
可控制參數:
- -XX:+UseCMS-CompactAtFullCollection:開啟標記整理,整理階段是沒辦法運行用戶線程的。
- -XX:CMSFullGCsBefore- Compaction:該參數可以設置次數,也就是gc幾次的時候,用一次標記整理,避免了非得到無法分配的情況才去整理,導致線程停頓。
3.8、G1收集器
特點:
優點:
G1收集器去跟蹤各個Region里面的垃 圾堆積的“價值”大小,維護一 個優先級列表,根據用戶設置的時間,優先處理回收價值收益最大的那些Region。說白了,就是你能接受的停頓時間大,收集器就能多釋放點內存,假如你設置的小,他就只能少釋放點,盡可能減少停頓時間,但是不可能控制的那么精準。
他跟CMS一樣,采用了分步來完成收集:
缺點:
可控制參數:
- -XX:G1HeapRegionSize:設置每個Region的大小
- -XX:MaxGCPauseMillis:設定允許的收集停頓時間
總結
以上是生活随笔為你收集整理的(七)Java垃圾收集器详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 注册 用户名 非法关键字限制
- 下一篇: 百度2017春招度度熊买帽子问题Java