java垃圾收集器與內(nèi)存分配策略詳解
1.經(jīng)典垃圾收集器
1.1 Serial收集器
這個收集器是一個單線程工作的收集器,但它的單線程的意義并不僅僅是說明他只會使用一個處理器或一條收集線程去完成垃圾收集工作,更重要對的是強調在它進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。
目前已經(jīng)老無可用,但有著優(yōu)于其他收集器的地方:簡單而高效
1.2 ParNew收集器
ParNew收集器實質上是Serial收集器的多線程并行版本。因為它是除了Serial收集器之外,目前唯一可以與CMS收集器配合工作的收集器,所以在JDK7之前的遺留系統(tǒng)中被作為首選的新生代收集器
CMS收集器是HotSpot虛擬機中第一款真正意義上支持并發(fā)的垃圾收集器,首次實現(xiàn)了讓垃圾收集線程與用戶線程同時工作。但是當選用CMS作為老年代收集器時,新生代收集器只能選擇使用Serial收集器或者ParNew收集器
隨著垃圾收集器技術的不斷改進,G1收集器帶著CMS繼承者和代替者的光環(huán)登場。G1收集器是一個面向全堆的收集器,不需要其他新生代收集器的配合工作
1.3 Parallel Scavenge 收集器
Parallel Scavenge收集器也是一款新生代收集器,同樣是基于標記-復制算法實現(xiàn)的收集器,也可以并行收集的多線程收集器。它的特點是它的關注點與其他收集器不同。CMS等收集器的關注點是盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。
$$
吞吐量=\frac{運行用戶代碼時間}{運行用戶代碼時間+運行垃圾收集時間}
$$
提供了兩個參數(shù)用于精確控制吞吐量:
-XX:MaxGCPauseMillis
參數(shù)控制最大垃圾搜集停頓時間,允許的值是一個大于0的毫秒數(shù)。收集器將盡力保證內(nèi)存回收花費的時間不超過用戶的設定值。但是設定過分小的值并不能起到加快回收花費的速度的作用。
-XX:GCTimeRatio
參數(shù)直接設置吞吐量大小,允許的值是一個大于0小于100的整數(shù)。也就是垃圾收集時間占總時間的比率。相當于吞吐量的倒數(shù)。
Parallel Scavenge 收集器還有一個參數(shù):-XX:+UseAdaptiveSizePolicy
這是一個開關參數(shù),當這個參數(shù)被激活以后,就不需要人工指定新生代的大小,Eden與Survivor區(qū)的比例等等。虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調整這些參數(shù)。
1.4 Serial Old 收集器
Serial Old 是 Serial收集器的老年代版本,同樣是一個單線程收集器,使用標記-整理算法??赡苡袃煞N用途:1. 在JDK5以及之前的版本中與Parallel Scavenge收集器搭配使用 2. 作為CMS收集器發(fā)生失敗時的后備預案。
1.5 Parallel Old 收集器
Parallel Old 是 Parallel Scavenge收集器的老年代版本,支持多線程并發(fā)收集,基于標記-整理算法實現(xiàn),從JDK6版本開始提供。在注重吞吐量或者處理器資源較為稀缺的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個組合。
1.6 CMS 收集器
CMS收集器是一種以獲取最短回收停頓時間為目標的收集器,基于標記-清除算法實現(xiàn)。整個運作過程分為4步:
步驟名稱 | 行為 |
---|---|
初始標記(CMS initial mark) | 標記一下GC Roots能直接關聯(lián)到的對象,需要Stop The World |
并發(fā)標記(CMS concurrent mark) | 從GC Roots的直接關聯(lián)對象開始遍歷整個對象圖的過程,可以與垃圾收集線程一起并發(fā)運行 |
重新標記(CMS remark) | 修正并發(fā)標記期間,因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分對象的標記記錄,需要Stop the World |
并發(fā)清楚(CMS concurrent sweep) | 清理刪除掉標記階段判斷的已經(jīng)死亡的對象,可以與用戶線程同時并發(fā)完成 |
CMS收集器存在三個缺點:
1.CMS收集器對處理器資源非常敏感,默認啟動的回收線程數(shù)為(處理器核心數(shù)量+3)/ 4。在并發(fā)階段會因為占用了一部分線程而導致應用程序變慢,降低總吞吐量。
為了緩解這種情況虛擬機提供了“增量式并發(fā)收集器”(Incremental Concurrent Mark Sweep/i-CMS)作用是在并發(fā)標記、清理的時候讓收集器線程、用戶線程交替運行,盡量減少垃圾收集器線程的獨占資源的時間,這樣整個垃圾收集的過程會更長,但是對用戶程序的影響就會顯得較少一些,直觀感受是速度變慢的時間更多了,但速度下降幅度就沒有那么明顯。效果一般從jdk7開始被聲明為deprecated ,從JDK9發(fā)布后被完全廢棄
2.由于CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現(xiàn)"Concurrent Mode Failure" 失敗進而導致另一完全"Stop The World"的Full GC的產(chǎn)生。
可以適當調高參數(shù)-XX:CMSInitiatingOccu-pancyFraction的值來提高CMS的觸發(fā)百分比,降低內(nèi)存回收頻率,獲得更好的性能。如果設置的太高將會很容易導致大量的并發(fā)失敗產(chǎn)生,性能反而降低
3.由于基于標記-清除算法,可能在收集結束時會有大量的空間碎片產(chǎn)生
通過調節(jié):-XX:+UseCMSCompactAtFullCollection開關參數(shù),默認是開啟的,從jdk9開始廢棄
*** -XX:CMSFullGCsBeforeCompaction 默認值是0,表示每次進入Full GC時都進行碎片整理***
1.7 Garbage First 收集器
Garbage First 收集器,簡稱 G1收集器,開創(chuàng)了收集器面向局部收集的設計思路和基于Region的內(nèi)存布局形式。是一款主要面向服務端應用的垃圾收集器。可以面向堆內(nèi)存的任何部分來組成回收集,衡量的標準不再是它屬于哪個分代,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大。G1開創(chuàng)的基于Region的堆內(nèi)存布局是它能夠實現(xiàn)這個目標的關鍵,G1不再堅持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的java堆劃分為多個大小相等的獨立區(qū)域(Region),每一個Region都可以根據(jù)需要,扮演新生代的Eden空間、Survivor空間或者老年代空間。
Region中還有一類特殊的Humongous區(qū)域,專門用來存儲大對象。G1認為只要大小超過了Region容量一半的對象就可以判定為大對象。每個Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設定,取值范圍為1MB~32MB。
G1收集器之所以可以建立可預測的停頓時間模型,是因為它將Region作為單次回收的最小單元,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍,這樣可以有計劃地避免在整個JAVA堆中進行全區(qū)域的垃圾收集。更具體的思路是讓G1收集器區(qū)跟蹤各個Region里面的垃圾堆積的價值大小,價值即回收所獲得的空間大小以及回收所需要的時間的經(jīng)驗值,然后在后臺 維護一個優(yōu)先級列表,每次根據(jù)用戶設定允許的收集停頓時間(-XX:MaxGCPauseMillis)優(yōu)先處理回收價值收益最大的那些Region。
G1的記憶集在存儲結構的本質上是一種哈希表,Key是別的Region的起始地址,Value是一個集合,里面存儲的元素是卡表的索引號。G1收集器通過原始快照(SATB)算法實現(xiàn)了保證其不能打破原本的對象圖結構的目的。
G1收集器運作過程大致分為四個步驟:
步驟 | 行為 |
---|---|
初始標記(Initial Marking) | 標記一下GC Roots能直接關聯(lián)到的對象,并且修改TAMS指針的值。這個階段需要停頓線程,而且是借用進行Minor GC的時候同步完成的 |
并發(fā)標記(Concurrent Marking) | 從GC Root開始對堆種對象進行可達性分析,遞歸掃描整個堆里的對象圖,找出要回收的對象,可以與用戶程序并發(fā)執(zhí)行。對象圖掃描完成以后,還需要重新處理SATB記錄下的在并發(fā)時有引用變動的對象 |
最終標記(Final Marking) | 對用戶線程做另一個短暫的暫停,用于處理并發(fā)階段結束后仍遺留下來的最后那少量的SATB記錄 |
篩選回收(Live Data Counting and Evacuation) | 負責更新Region的統(tǒng)計數(shù)據(jù),對各個Region的回收價值和成本進行排序,根據(jù)用戶所期望的停頓時間來制定回收計劃,可以自由選擇任意多個Region構成回收集,然后把決定回收的那一部分Region的存活對象復制到空的Region中,再清理整個舊Region的全部空間,必須暫停用戶線程 |
2低延遲垃圾收集器
2.1 Shenandoah收集器
Shenandoah收集器是一款只有OpenJDK才會包含的。與G1收集器相比,它們兩者有著相似的堆內(nèi)存布局,在初始標記、并發(fā)標記等許多階段的處理思路上都高度一致。但是在管理內(nèi)存堆方面,與G1收集器至少有三個方面的明顯的不同之處:
1.支持并發(fā)的整理算法:G1的回收階段是可以多線程并行的,但不能與用戶線程并發(fā)。Shenandoah后面會講到。
2.Shenandoah收集器默認不使用分代收集。
3.Shenandoah摒棄了在G1中耗費大量內(nèi)存和計算資源去維護的記憶集,改名為“連接矩陣”(Connection Matrix)的全局數(shù)據(jù)結構來記錄跨Region的引用關系。降低了處理跨代指針的記憶集維護消耗,也降低了偽共享問題發(fā)生的概率
Shenandoah收集器大致工作流程可以分為9個階段:
步驟名稱 | 動作 |
---|---|
初始標記(Initial Marking) | 標記與GC Roots直接關聯(lián)的對象,這個階段是Stop The World的,停頓時長與堆大小無關,與GC Roots的數(shù)量相關。 |
并發(fā)標記(Concurrent Marking) | 遍歷對象圖,標記出全部可達的對象,這個階段與用戶線程一起并發(fā)的,時間長短取決于堆中存活對象的數(shù)量以及對象圖的結構復雜程度。 |
最終標記(Final Marking) | 處理剩余的SATB掃描,在這個階段統(tǒng)計出回收價值最高的Region,將這些Region構成一組回收集,最終標記階段也會有一小段短暫的停頓。 |
并發(fā)清理(Concurrent Cleanup) | 清理那些整個區(qū)域內(nèi)連一個存活對象都沒有找到的Region(這類Region被稱為Immediate Garbage Region)。 |
并發(fā)回收(Concurrent Evacuation) | 核心差異!Shenandoah要把回收集里面的存活對象先復制一份到其他未被使用的Region之中。Shenandoah會通過讀屏障和被成稱為"Brooks Points"的轉發(fā)指針來解決在復制對象時遇到的困難。時間長短取決于回收集的大小。 |
初始引用更新(Initial Update Reference) | 把堆中所有指向舊對象的引用修正到復制后的新地址,這個操作稱為引用更新。在此階段,只是建立了一個線程集合點,確保所有的并發(fā)回收階段中進行的收集器線程都已完成分配給它們的對象移動任務而已。時間會很短,有一個十分短暫的停頓。 |
并發(fā)引用更新(Comcurrent Update Reference) | 真正開始進行引用更新操作,與用戶線程一起并發(fā)的,時間長短取決于內(nèi)存中涉及的引用數(shù)量的多少。只需要按照內(nèi)存物理地址的順序,線性搜索出引用類型,把舊值改為新值即可。 |
最終引用更新(Final Update Reference) | 解決了堆中的引用更新后,還要修正存在于GC Roots中的引用。這個階段是最后一次停頓,時間與GC Roots的數(shù)量有關。 |
并發(fā)清理(Concurrent Cleanup) | 此時整個回收集中所有的Region已再無存活對象,都變成了Immediate Garbage Regions了,最后調用一次并發(fā)清理過程來回收這些Region的內(nèi)存空間,供以后新對象分配使用。 |
Brooks Points:Brooks是一個人的名字,它提出使用了轉發(fā)指針(Forwarding Pointer)的技術來實現(xiàn)對象移動與用戶程序并發(fā)的一種解決方案。不需要用到內(nèi)存保護陷阱,而是在原有對象布局結構的最前面統(tǒng)一增加一個新的引用字段,在正常不處于并發(fā)移動的情況下,該引用指向對象自己。實際上Shenandoah收集器是通過比較并交換(Compare And Swap, CAS)操作來保證并發(fā)時對象的訪問正確性的。
JDK13中Shenandoah的內(nèi)存屏障模型改進為基于引用訪問屏障(Load Reference Barrier)的實現(xiàn),所謂“引用訪問屏障”是指內(nèi)存屏障只攔截對象中數(shù)據(jù)類型為引用類型的讀寫操作,而不去管原生數(shù)據(jù)類型等其他非引用字段的讀寫。這能省去大量對原生類型、對象比較、對象加鎖等場景中設置內(nèi)存屏障所帶來的消耗。
2.2 ZGC收集器
ZGC收集器是一款基于Region內(nèi)存布局的,暫時不設分代的,使用了讀屏障、染色指針和內(nèi)存多重映射等技術來實現(xiàn)可并發(fā)的標記-整理算法的,以低延遲為首要目標的一款垃圾收集器。
ZGC的Region具有動態(tài)性-動態(tài)創(chuàng)建和銷毀,以及動態(tài)的區(qū)域容量大小。
染色指針(Colored Pointer):一種直接將少量額外的信息存儲在指針上的技術。盡管在linux下64位指針的高18位不能用來尋址,但是剩余的46位所能支持的64TB內(nèi)存仍然能夠充分滿足需要。鑒于此,將其高4位提取出來存儲四個標記信息。通過這些標志位,虛擬機可以直接從指針中看到其引用對象的三色標記狀態(tài)、是否進入了重分配集、是否只能通過finalize( )方法才能被訪問到。也使得ZGC能夠管理的內(nèi)存不可以超過4TB。使用染色指針的三大優(yōu)勢:
1.可以使得一旦某個Region的存活對象被移走之后,這個Region立即就能被釋放和重用掉,不必等待整個堆中所有指向該Region的引用都被修正后才能清理。
2.可以大幅度減少在垃圾收集過程中的內(nèi)存屏障的使用數(shù)量。到目前為止,ZGC都未使用寫屏障,只使用了讀屏障。
3.可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數(shù)據(jù),以便日后進一步提高性能。
Linux/x86-64平臺上的ZGC使用了多重映射將多個不同的虛擬內(nèi)存地址映射到同一個物理內(nèi)存地址上,意味著ZGC在虛擬內(nèi)存中看到的地址空間要比實際的堆內(nèi)存容量來得更大。把染色指針中的標志位看作是地址的分段符,只要將這些不同的地址段都映射到同一個物理內(nèi)存空間,經(jīng)過多重映射轉換后,就可以使用染色指針正常進行尋址了。
ZGC的運作過程(省略部分與之前介紹的G1和Shenandoah相同的小階段部分):
步驟 | 動作 |
---|---|
并發(fā)標記(Concurrent Mark) | 遍歷對象圖做可達性分析的階段,前后也要經(jīng)過初始標記、最終標記的短暫停頓。ZGC的標記是在指針上而不是在對象上進行的,標記階段會更新染色指針中的Marked 0、Marked 1 標志位。 |
并發(fā)預備重分配(Concurrent Prepare for Relocate) | 根據(jù)特定的查詢條件統(tǒng)計得出本次收集過程要清理那些Region,將這些Region組成重分配集。與G1收集器的回收集還是有區(qū)別的,ZGC的重分配集只是決定了里面的存活對象會被重新分配復制到其他的Region中,里面的Region會被釋放,而并不能說回收行為就只是針對這個集合里面的Region進行,因為標記過程是針對全堆的。 |
并發(fā)重分配(Concurrent Reolcate) | 核心階段!把重分配集中的存活對象復制到新的Region上,并未重分配集中的每個Region維護一個轉發(fā)表,記錄從舊對象到新對象的轉向關系。 |
并發(fā)重映射(Concurrent Remap) | 修正整個堆中指向重分配集中舊對象的所有引用。 |
總結
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關注腳本之家的更多內(nèi)容!
相關文章
Java泛型T,E,K,V,N,?與Object區(qū)別和含義
Java?泛型(generics)是?JDK?5?中引入的一個新特性,?泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。本文將詳細講講Java泛型T、E、K、V、N、?和Object區(qū)別和含義,需要發(fā)可以參考一下2022-03-03Java 創(chuàng)建線程的3種方法及各自的優(yōu)點
這篇文章主要介紹了Java 創(chuàng)建線程的3種方法及各自的優(yōu)點,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07關于MyBatis 查詢數(shù)據(jù)時屬性中多對一的問題(多條數(shù)據(jù)對應一條數(shù)據(jù))
這篇文章主要介紹了MyBatis 查詢數(shù)據(jù)時屬性中多對一的問題(多條數(shù)據(jù)對應一條數(shù)據(jù)),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01