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