深入理解JVM虚拟机读书笔记——垃圾回收器
注:本文參考自周志明老師的著作《深入理解Java虛擬機(jī)(第3版)》,相關(guān)電子書可以關(guān)注WX公眾號(hào),回復(fù) 001 獲取。
如果說收集算法是內(nèi)存回收的方法論,那垃圾收集器就是內(nèi)存回收的實(shí)踐者。《Java虛擬機(jī)規(guī)范》中對(duì)垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有做出任何規(guī)定,因此不同的廠商、不同版本的虛擬機(jī)所包含的垃圾收集器都可能會(huì)有很大差別,不同的虛擬機(jī)一般也都會(huì)提供各種參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)內(nèi)存分代所使用的收集器。
各款經(jīng)典收集器之間的關(guān)系如下圖所示:
如圖展示了七種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說明它們可以搭配使用,圖中收集器所處的區(qū)域,則表示它是屬于新生代收集器抑或是老年代收集器。下面來逐個(gè)了解一下不同款的垃圾收集器:
1. Serial收集器
Serial收集器是最基礎(chǔ)、歷史最悠久的收集器,它是一個(gè)單線程工作的收集器。這里的“單線程”的意義并不僅僅是說明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作,更重要的是強(qiáng)調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束(“Stop The World”)。
使用Serial收集器進(jìn)行垃圾回收時(shí),新生代收集采用標(biāo)記-復(fù)制算法,老年代收集采用標(biāo)記-整理算法,收集流程如圖所示:
對(duì)于這種垃圾收集器,進(jìn)行垃圾回收時(shí)出現(xiàn)的“Stop The World”,它暫停所有用戶線程的行為顯然是不可接受的,但是在HotSpot虛擬機(jī)設(shè)計(jì)之初,也是無(wú)奈之舉,原書中給出的解釋如下:
對(duì)于“Stop The World”帶給用戶的惡劣體驗(yàn),早期HotSpot虛擬機(jī)的設(shè)計(jì)者們表示完全理解,但也 同時(shí)表示非常委屈:“你媽媽在給你打掃房間的時(shí)候,肯定也會(huì)讓你老老實(shí)實(shí)地在椅子上或者房間外待 著,如果她一邊打掃,你一邊亂扔紙屑,這房間還能打掃完?”這確實(shí)是一個(gè)合情合理的矛盾,雖然垃 圾收集這項(xiàng)工作聽起來和打掃房間屬于一個(gè)工種,但實(shí)際上肯定還要比打掃房間復(fù)雜得多!雖然Serial收集器的“單線程收集”是有明顯的弊端的,但是,他自身的優(yōu)勢(shì)也是很明顯的:
即,簡(jiǎn)單而高效,對(duì)于內(nèi)存資源受限的環(huán)境,它是所有收集器里額外內(nèi)存消耗最小的;對(duì)于單核處理器或處理器核心數(shù)較少的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。
在用戶桌面的應(yīng)用場(chǎng)景以及近年來流行的部分微服務(wù)應(yīng)用中,分配給虛擬機(jī)管理的內(nèi)存一般來說并不會(huì)特別大,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的內(nèi)存,桌面應(yīng)用甚少超過這個(gè)容量),垃圾收集的停頓時(shí)間完全可以控制在十幾、幾十毫秒,最多一百多毫秒以內(nèi),只要不是頻繁發(fā)生收集,這點(diǎn)停頓時(shí)間對(duì)許多用戶來說是完全可以接受的。所以,Serial收集器對(duì)于運(yùn)行在客戶端模式下的虛擬機(jī)來說是一個(gè)很好的選擇。
2. ParNew收集器
ParNew收集器實(shí)質(zhì)上是Serial收集器的多線程并行版本,除了同時(shí)使用多條線程進(jìn)行垃圾收集之外,其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一致,在實(shí)現(xiàn)上這兩種收集器也共用了相當(dāng)多的代碼。ParNew收集器的工作過程如下圖所示:
ParNew收集器除了支持多線程并行收集之外,其他與Serial收集器相比并沒有太多創(chuàng)新之處,但它卻是不少運(yùn)行在服務(wù)端模式下的HotSpot虛擬機(jī)。目前只有ParNew收集器能與CMS收集器(后面再介紹)配合工作。
注意:從ParNew收集器開始,后面還將會(huì)接觸到若干款涉及“并發(fā)”和“并行”概念的收集器。有必要先解釋清楚這兩個(gè)名詞。
在談?wù)摾占鞯纳舷挛恼Z(yǔ)境中,它們可以理解為:
- 并行(Parallel):并行描述的是多條垃圾收集器線程之間的關(guān)系,說明同一時(shí)間有多條這樣的線程在協(xié)同工作,通常默認(rèn)此時(shí)用戶線程是處于等待狀態(tài)。
- 并發(fā)(Concurrent):并發(fā)描述的是垃圾收集器線程與用戶線程之間的關(guān)系,說明同一時(shí)間垃圾收集器線程與用戶線程都在運(yùn)行。由于用戶線程并未被凍結(jié),所以程序仍然能響應(yīng)服務(wù)請(qǐng)求,但由于垃圾收集器線程占用了一部分系統(tǒng)資源,此時(shí)應(yīng)用程序的處理的吞吐量將受到一定影響。
3. Parallel Scavenge收集器
Parallel Scavenge收集器是一款新生代收集器,它是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器,能夠并行收集的多線程收集器。
Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)。所謂吞吐量就是處理器用于運(yùn)行用戶代碼的時(shí)間與處理器總消耗時(shí)間的比值,即:
如果虛擬機(jī)完成某個(gè)任務(wù),用戶代碼加上垃圾收集總共耗費(fèi)了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
停頓時(shí)間越短就越適合需要與用戶交互或需要保證服務(wù)響應(yīng)質(zhì)量的程序,良好的響應(yīng)速度能提升用戶體驗(yàn);而高吞吐量則可以最高效率地利用處理器資源,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的分析任務(wù)。
Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù),以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。
由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也經(jīng)常被稱作“吞吐量?jī)?yōu)先收集器”。除上述兩個(gè)參數(shù)之外,Parallel Scavenge收集器還有一個(gè)參數(shù)-XX:+UseAdaptiveSizePolicy值得我們關(guān)注。這是一個(gè)開關(guān)參數(shù),當(dāng)這個(gè)參數(shù)被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden與Survivor區(qū)的比例(-XX:SurvivorRatio)、晉升老年代對(duì)象大小(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量。這種調(diào)節(jié)方式稱為垃圾收集的自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)。
4. Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用標(biāo)記-整理算法,主要是供客戶端模式下的HotSpot虛擬機(jī)使用。
如果在服務(wù)端模式下,它也可能有兩種用途:一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種就是作為CMS收集器發(fā)生失敗時(shí)的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用。這兩點(diǎn)都將在后面的內(nèi)容中繼續(xù)講解。
Serial/Serial Old收集器運(yùn)行示意圖如下所示:
注意:
Parallel Scavenge收集器架構(gòu)中本身有PS MarkSweep收集器來進(jìn)行老年代收集,并非直接調(diào)用Serial Old收集器,但是這個(gè)PS MarkSweep收集器與Serial Old的實(shí)現(xiàn)幾乎是一樣的,所以在官方的許多資料中都是直接以Serial Old代替PS MarkSweep。
5. Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實(shí)現(xiàn)。這個(gè)收集器是直到JDK 6時(shí)才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處于相當(dāng)尷尬的狀態(tài),原因是如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外別無(wú)選擇,其他表現(xiàn)良好的老年代收集器,如CMS無(wú)法與它配合工作。
由于老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的“拖累”,使用Parallel Scavenge收集器也未必能在整體上獲得吞吐量最大化的效果。同樣,由于單線程的老年代收集中無(wú)法充分利用服務(wù)器多處理器的并行處理能力,在老年代內(nèi)存空間很大而且硬件規(guī)格比較高級(jí)的運(yùn)行環(huán)境中,這種組合的總吞吐量甚至不一定比ParNew加CMS的組合來得優(yōu)秀。
直到Parallel Old收集器出現(xiàn)后,“吞吐量?jī)?yōu)先”收集器終于有了比較名副其實(shí)的搭配組合,在注重吞吐量或者處理器資源較為稀缺的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組合。
Parallel Scavenge/Parallel Old收集器運(yùn)行示意圖:
介紹完上面5種經(jīng)典的垃圾收集器后,下面要介紹的兩種則是更加先進(jìn),且使用普遍的經(jīng)典垃圾收集器,其中G1垃圾回收器更是使用最普遍的。
6. CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。
目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用通常都會(huì)較為關(guān)注服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間盡可能短,以給用戶帶來良好的交互體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求。
CMS收集器是基于標(biāo)記-清除算法實(shí)現(xiàn)的,它的整個(gè)運(yùn)作過程分為四個(gè)步驟:
- 1)初始標(biāo)記(CMS initial mark)
- 2)并發(fā)標(biāo)記(CMS concurrent mark)
- 3)重新標(biāo)記(CMS remark)
- 4)并發(fā)清除(CMS concurrent sweep)
初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”。
一、初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快;
二、并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程,這個(gè)過程耗時(shí)較長(zhǎng)但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行;
三、重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄(詳見3.4.6節(jié)中關(guān)于增量更新的講解),這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長(zhǎng)一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短;
四、并發(fā)清除階段,清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象,由于不需要移動(dòng)存活對(duì)象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。
由于在整個(gè)過程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。
通過下圖(Concurrent Mark Sweep收集器運(yùn)行示意圖 )可以比較清楚地看到CMS收集器的運(yùn)作步驟中并發(fā)和需要停頓的階段:
停頓時(shí)間短、響應(yīng)速度快是CMS收集器的重要優(yōu)勢(shì),而它也并非完美的垃圾收集器,其主要有以下幾個(gè)缺點(diǎn):
-
CMS是一款基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。
-
CMS收集器對(duì)處理器資源非常敏感(面向并發(fā)設(shè)計(jì)的程序都對(duì)處理器資源比較敏感)。在并發(fā)標(biāo)記階段,它雖然不會(huì)導(dǎo)致用戶線程停頓,但卻會(huì)因?yàn)檎加昧艘徊糠志€程(或者說處理器的計(jì)算能力)而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量。
-
CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(處理器核心數(shù)量 + 3)/ 4,也就是說,如果處理器核心數(shù)在四個(gè)或以上,并發(fā)回收時(shí)垃圾收集線程只占用不超過25%的
處理器運(yùn)算資源,并且會(huì)隨著處理器核心數(shù)量的增加而下降。
-
但是當(dāng)處理器核心數(shù)量不足四個(gè)時(shí), CMS對(duì)用戶程序的影響就可能變得很大。如果應(yīng)用本來的處理器負(fù)載就很高,還要分出一半的運(yùn)算能力去執(zhí)行收集器線程,就可能導(dǎo)致用戶程序的執(zhí)行速度忽然大幅降低。
-
7. Garbage First收集器
Garbage First(簡(jiǎn)稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果,JDK 8 Update 40 版本之后,G1收集器被Oracle官方稱為“全功能的垃圾收集 器”(Fully-Featured Garbage Collector)。
G1是一款主要面向服務(wù)端應(yīng)用的垃圾收集器。JDK 9之后,G1取代了Parallel Scavenge加Parallel Old組合,成為服務(wù)端模式下的默認(rèn)垃圾收集器。
G1不再堅(jiān)持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),每一個(gè)Region都可以根據(jù)需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠?qū)Π缪莶煌巧腞egion采用不同的策略去處理,這樣無(wú)論是新創(chuàng)建的對(duì)象還是已經(jīng)存活了一段時(shí)間、熬過多次收集的舊對(duì)象都能獲取很好的收集效果。
Region中還有一類特殊的Humongous區(qū)域,專門用來存儲(chǔ)大對(duì)象。G1認(rèn)為只要大小超過了一個(gè)Region容量一半的對(duì)象即可判定為大對(duì)象。每個(gè)Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設(shè)定,取值范圍為 1MB~32MB,且應(yīng)為2的N次冪。而對(duì)于那些超過了整個(gè)Region容量的超級(jí)大對(duì)象,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region之中,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分來進(jìn)行看待,如下圖(G1收集器Region分區(qū)示意圖)所示:
雖然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它們都是一系列區(qū)域(不需要連續(xù))的動(dòng)態(tài)集合。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗鼘egion作為單次回收的最小單元,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍,這樣可以有計(jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。
更具體的處理思路是讓G1收集器去跟蹤各個(gè)Region里面的垃圾堆積的“價(jià)值”大小,價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間(使用參數(shù)-XX:MaxGCPauseMillis指定,默認(rèn)值是200毫秒),優(yōu)先處理回收價(jià)值收益最大的那些Region,這也就是“Garbage First”名字的由來。
這種使用Region劃分內(nèi)存空間,以及具有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限的時(shí)間內(nèi)獲取盡可能高的收集效率。
G1收集器的運(yùn)作過程大致可劃分為以下四個(gè)步驟:
-
初始標(biāo)記(Initial Marking):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對(duì)象。這個(gè)階段需要停頓線程,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的,所以G1收集器在這個(gè)階段實(shí)際
并沒有額外的停頓。
-
并發(fā)標(biāo)記(Concurrent Marking):從GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。當(dāng)對(duì)象圖掃描完成以后,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象。
-
最終標(biāo)記(Final Marking):對(duì)用戶線程做另一個(gè)短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留下來的最后那少量的SATB記錄。
-
篩選回收(Live Data Counting and Evacuation):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃,可以自由選擇任意多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中,再清理掉整個(gè)舊Region的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程,由多條收集器線程并行完成的。
從上述階段的描述可以看出,G1收集器除了并發(fā)標(biāo)記外,其余階段也是要完全暫停用戶線程的,換言之,它并非純粹地追求低延遲,官方給它設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量,所以才能擔(dān)當(dāng)起“全功能收集器”的重任與期望。
G1收集器運(yùn)行示意圖:
通過上圖可以比較清楚地看到G1收集器的運(yùn)作步驟中并發(fā)和需要停頓的階段。
擴(kuò)展:低延遲垃圾收集器
衡量垃圾收集器的三項(xiàng)最重要的指標(biāo)是:內(nèi)存占用(Footprint)、吞吐量(Throughput)和延遲(Latency),三者共同構(gòu)成了一個(gè)“不可能三角[1]”。三者總體的表現(xiàn)會(huì)隨技術(shù)進(jìn)步而越來越好,但是要在這三個(gè)方面同時(shí)具有卓越表現(xiàn)的“完美”收集器是極其困難甚至是不可能的,一款優(yōu)秀的收集器通常最多可以同時(shí)達(dá)成其中的兩項(xiàng)。
HotSpot的垃圾收集器從Serial發(fā)展到CMS再到G1,性能不斷改進(jìn),但是沒有一款收集器可以做到盡善盡美,隨著收集器的發(fā)展,目前也有了一些新的垃圾收集器,例如:Shenandoah收集器、ZGC收集器,這兩種收集器感興趣的小伙伴可以自己買本書或者找一些博客去進(jìn)一步去了解一下。
內(nèi)存分配與回收策略(重要)
Java技術(shù)體系的自動(dòng)內(nèi)存管理,最根本的目標(biāo)是自動(dòng)化地解決兩個(gè)問題:自動(dòng)給對(duì)象分配內(nèi)存以及自動(dòng)回收分配給對(duì)象的內(nèi)存。
在經(jīng)典分代的設(shè)計(jì)下,新生對(duì)象通常會(huì)分配在新生代中,少數(shù)情況下(例如對(duì)象大小超過一定閾值)也可能會(huì)直接分配在老年代。對(duì)象分配的規(guī)則并不是固定的,
《Java虛擬機(jī)規(guī)范》并未規(guī)定新對(duì)象的創(chuàng)建和存儲(chǔ)細(xì)節(jié),這取決于虛擬機(jī)當(dāng)前使用的是哪一種垃圾收集器,以及虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的設(shè)定。
下面來具體了解下內(nèi)存分配與回收相關(guān)幾個(gè)要點(diǎn):
一、對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC。
二、大對(duì)象直接進(jìn)入老年代
大對(duì)象就是指需要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象便是那種很長(zhǎng)的字符串,或者元素?cái)?shù)量很龐大的數(shù)組。
大對(duì)象對(duì)虛擬機(jī)的內(nèi)存分配來說就是一個(gè)不折不扣的壞消息,比遇到一個(gè)大對(duì)象更加壞的消息就是遇到一群“朝生夕滅”的“短命大對(duì)象”,我們寫程序的時(shí)候應(yīng)注意避免。在Java虛擬機(jī)中要避免大對(duì)象的原因是,在分配空間時(shí),它容易導(dǎo)致內(nèi)存明明還有不少空間時(shí)就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們,而當(dāng)復(fù)制對(duì)象時(shí),大對(duì)象就意味著高額的內(nèi)存復(fù)制開銷。
HotSpot虛擬機(jī)提供了 -XX:PretenureSizeThreshold 參數(shù),指定大于該設(shè)置值的對(duì)象直接在老年代分配,這樣做的目的就是避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間來回復(fù)制,產(chǎn)生大量的內(nèi)存復(fù)制操作。
三、長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
HotSpot虛擬機(jī)中多數(shù)收集器都采用了分代收集來管理堆內(nèi)存,為了便于虛擬機(jī)決策哪些存活對(duì)象應(yīng)當(dāng)放在新生代,哪些存活對(duì)象放在老年代中,虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器,存儲(chǔ)在對(duì)象頭中。
對(duì)象通常在Eden區(qū)里誕生,如果經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納的話,該對(duì)象會(huì)被移動(dòng)到Survivor空間中,并且將其對(duì)象年齡設(shè)為1歲。對(duì)象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15),就會(huì)被晉升到老年代中。對(duì)象晉升老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 設(shè)置。
四、動(dòng)態(tài)對(duì)象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,HotSpot虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到 -XX:MaxTenuringThreshold 才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到 -XX:MaxTenuringThreshold 中要求的年齡。
五、空間分配擔(dān)保
在發(fā)生Minor GC之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那這一次Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)先查看 -XX:HandlePromotionFailure 參數(shù)的設(shè)置值是否允許擔(dān)保失敗(Handle Promotion Failure);
如果允許,那會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于,或者 -XX:HandlePromotionFailure 設(shè)置不允許冒險(xiǎn),那這時(shí)就要改為進(jìn)行一次Full GC。
解釋一下“冒險(xiǎn)”是冒了什么風(fēng)險(xiǎn):
新生代使用復(fù)制收集算法,但為了內(nèi)存利用率,只使用其中一個(gè)Survivor空間來作為輪換備份,因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況——最極端的情況就是內(nèi)存回收后新生代中所有對(duì)象都存活,需要老年代進(jìn)行分配擔(dān)保,把Survivor無(wú)法容納的對(duì)象直接送入老年代,這與生活中貸款擔(dān)保類似。老年代要進(jìn)行這樣的擔(dān)保,前提是老年代本身還有容納這些對(duì)象的剩余空間,但一共有多少對(duì)象會(huì)在這次回收中活下來在實(shí)際完成內(nèi)存回收之前是無(wú)法明確知道的,所以只能取之前每一次回收晉升到老年代對(duì)象容量的平均大小作為經(jīng)驗(yàn)值,與老年代的剩余空間進(jìn)行比較,決定是否進(jìn)行Full GC來讓老年代騰出更多空間。
取歷史平均值來比較其實(shí)仍然是一種賭概率的解決辦法,也就是說假如某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于歷史平均值的話,依然會(huì)導(dǎo)致?lián)J H绻霈F(xiàn)了擔(dān)保失敗,那就只好老老實(shí)實(shí)地重新發(fā)起一次Full GC,這樣停頓時(shí)間就很長(zhǎng)了。
雖然擔(dān)保失敗時(shí)繞的圈子是最大的,但通常情況下都還是會(huì)將 -XX:HandlePromotionFailure 開關(guān)打開,避免Full GC過于頻繁。
結(jié)尾:
非常建議學(xué)習(xí)Java的小伙伴,買一本周志明老師的《深入理解Java虛擬機(jī)(第3版)》去讀一讀,博客和視頻教程,始終不如看書來得實(shí)在呀!
后續(xù)會(huì)陸續(xù)更新,這本書的筆記記的差不多了,排版和格式需要花時(shí)間整理,文章都會(huì)同步到公眾號(hào)上,也歡迎大家通過公眾號(hào)加入我的交流qun互相討論jvm這塊的知識(shí)內(nèi)容!
總結(jié)
以上是生活随笔為你收集整理的深入理解JVM虚拟机读书笔记——垃圾回收器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: while语法php格式,PHP Whi
- 下一篇: 78--规格参数的查询与修改功能