G1收集器
G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征。一般G1收集器是用在8G以上內存的服務器上的,jdk9將它設為默認收集器。
G1內存中年輕代老年代結構和之前不同,以前年輕代老年代是有物理隔離的。G1現在年輕代和老年代只是一個邏輯概念,每一塊區域都可能是年輕代,也可能之后會變成老年代。
它會將Java的堆分為多個大小相當的獨立區域(稱為Region),默認的情況下,是堆大小除以2048,即4G的堆的話,一塊Region是2M大小。
默認情況下年輕代占比為5%,在系統運行中,jvm也會不斷調整,增加年輕代的區域,但是默認不超過60%。年輕代中eden區和s區的大小比例依舊是8:1:1。
G1收集器對於對象什么時候轉到老年區是和原來的原則一樣的,唯一不同的是對大對象的處理,原先超過設定閾值大小的對象,會直接進入老年代。現在是有個專門的大對象保存區Humongous區,如果一個對象大小超過每個Region的50%,那么會直接進入Humongous中,Humorous卡頁跨多個Region存放。
Humongous區專門存放短期巨型對象,不用直接進老年代,可以節約老年代的空間,避免因為老年代空間不夠的GC開銷。
Full GC的時候除了收集年輕代和老年代之外,也會將Humongous區一並回收。
G1的gc過程如下:
1.初始標記
2.並發標記
3.最終標記
4.篩選回收
1,2,3與CMS很像就不細說了,4的時候會開始垃圾回收,CMS的垃圾回收是與用戶線程同步的,但是G1由於內部實現太復雜,所以沒有實現並發回收。但是G1可以設定一個參數(可以用JVM參數 -XX:MaxGCPauseMillis指定),這個參數可以設定gc的停頓時間,它會根據這個停頓時間,來判斷垃圾回收到什么程度。即G1不一定會把垃圾全部回收完,當它回收到一定程度,停頓時間到了,他就會停止回收,故它可能只會回收80%的垃圾,就繼續讓客戶線程運行了。
回收算法主要用的是復制算法,將一個region中的存活對象復制到另一個region中,這種不會像CMS那樣
回收完因為有很多內存碎片還需要整理一次,G1采用復制算法回收幾乎不會有太多內存碎片。
G1的垃圾收集分類
YoungGC
與其他的收集器的Minor gc不同,它不是在Eden區放滿了觸發,G1會計算下現在Eden區回收垃圾大概要多久時間,如果回收時間遠遠小於參數 -XX:MaxGCPauseMills 設定的值,那么G1會選擇繼續增加年輕代的region,繼續存放新對象,直到下一次Eden區放滿,G1計算到回收時間接近參數 -XX:MaxGCPauseMills 設定的值,才會觸發Young GC
MixedGC
類似之前其他的收集器的full gc,mixed gc會收集老年代,但是它不是FullGC,它會在老年代的堆占有率達到參數(-XX:InitiatingHeapOccupancyPercent)設定的值則觸發,回收所有的年輕代和部分老年代(根據期望的GC停頓時間確定老年代垃圾收集的優先順序)以及大對象區,正常情況G1的垃圾收集是先做MixedGC,主要使用復制算法,需要把各個region中存活的對象拷貝到別的region里去,拷貝過程中如果發現沒有足夠的空間去承載被拷貝的對象就會觸發一次Full GC
Full GC
這個時候會停止用戶的線程,采用單線程的方式去gc回收,這個過程十分耗時。
G1收集器參數
-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的線程數量
-XX:G1HeapRegionSize:指定分區大小(1MB~32MB,且必須是2的N次冪),默認將整堆划分為2048個分區
-XX:MaxGCPauseMillis:目標暫停時間(默認200ms)
-XX:G1NewSizePercent:新生代內存初始空間(默認整堆5%)
-XX:G1MaxNewSizePercent:新生代內存最大空間
-XX:InitiatingHeapOccupancyPercent:老年代占用空間達到整堆內存閾值(默認45%),則執行MixedGC,比如我們之前說的堆默認有2048個region,如果有接近1000個region都是老年代的region,則可能就要觸發MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默認85%) region中的存活對象低於這個值時才會回收該region,如果超過這個值,存活對象過多,回收的的意義不大。
-XX:G1MixedGCCountTarget:在一次回收過程中指定做幾次篩選回收(默認8次),在篩選回收階段,G1可以回收一會,然后暫停回收,放用戶線程運行一會,一會再開始回收,這樣可以讓系統不至於單次停頓時間過長,這個參數可以設置暫停的次數,如果系統垃圾不多可以設小一點。
-XX:G1HeapWastePercent(默認5%): gc過程中空出來的region是否充足閾值,主要是用來判斷什么時候停止mixed gc的,在mixed gc的時候,回收過程會一直清理空間,一旦清理出來的空閑Region數量達到了堆內存的5%,此時就會立即停止mixed gc。
ZGC收集器
ZGC收集器是jdk11中加入的收集器,目前還不完善,它支持TB級內存的垃圾收集,且最大GC停頓時間不超過10ms,它的堆內存結構是不分代的。而是分為了小內存(2M),中內存(32M)和大內存(2N M)。
染色指針
ZGC和CMS、G1不同的地方在於,CMS、G1是將三色標記做在對象上的,ZGC是做在指針上的。目前Linux下64位指針的高18位不能用來尋址,剩余的46位指針其結構為:
1位:Finalizable標識,此位與並發引用處理有關,它表示這個對象只能通過finalizer才能訪問;
1位:Remapped標識,設置此位的值后,對象未指向relocation set中(relocation set表示需要GC的
Region集合);
1位:Marked1標識;
1位:Marked0標識,和上面的Marked1都是標記對象用於輔助GC(三色標記);
42位:對象的地址(所以它最大支持2^42=4T內存
為什么有2個mark標記?每一個GC周期開始時,會交換使用的標記位,使上次GC周期中修正的已標記狀態失效,所有引用都變成未標記。
GC周期1:使用mark0, 則周期結束所有引用mark標記都會成為01。
GC周期2:使用mark1, 則期待的mark標記10,所有引用都能被重新標記。
染色指針的優勢
1.染色指針可以使得一旦某個Region的存活對象被移走之后,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該Region的引用都被修正后才能清理。
2.染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況,如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作。
3.染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日后進一步提高性能。
ZGC的執行過程
1.並發標記(Concurrent Mark): 與G1一樣,並發標記是遍歷對象圖做可達性分析的階段,前后也要經過類似於G1的初始標記、最終標記的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。不同的是ZGC標記的是指針而不是對象。
2.並發預備重分配( Concurrent Prepare for Relocate): 這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。
3.並發重分配(Concurrent Relocate): 重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象復制到新的Region上,並為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關系。
4.並發重映射(Concurrent Remap): 重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,ZGC的並發映射並不是以一個必須要“迫切”去完成的任務。ZGC很巧妙地把並發重映射階段要做的工作,合並到下一次垃圾收集循環中的並發標記階段里去完成,反正他們都是要遍歷所有對象的,這樣合並節省了一次遍歷的開銷。
ZGC的優劣勢
缺點:浮動垃圾
當ZGC准備要對一個很大的堆做一次完整的並發收集,駕駛其全過程要持續十分鍾以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記范圍,通常就只能全部作為存活對象來看待(盡管其中絕大部分對象都是朝生夕滅),這就產生了大量的浮動垃圾。
目前唯一的辦法就是盡可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然后針對這個區域進行更頻繁、更快的收集。
優點:高吞吐量、低延遲。
ZGC是支持“NUMA-Aware”的內存分配。MUMA(Non-Uniform Memory Access,非統一內存訪問架構)是一種多處理器或多核處理器計算機所設計的內存架構。
現在多CPU插槽的服務器都是Numa架構,Numa的架構是所有CPU去爭搶一個內存,那么肯定會出現並發的問題和鎖的問題,因此速度會慢。
ZGC默認支持NUMA架構,在創建對象時,根據當前線程在哪個CPU執行,優先在靠近這個CPU的內存進行分配,這樣可以顯著的提高性能。
記憶集與卡表
記憶集的作用是,gc root的時候可能會遇到跨代引用的問題,即老年代引用了年輕代。gc root指的是內存中的靜態變量,和臨時變量,這樣的話是沒辦法得到老年代中的對象引用情況的。如果沒有別的辦法,只能一個個去掃老年代,這樣效率十分低下。因此加入了記憶集這個數據結構,它的作用是記錄從非收集區到收集區的指針集合,避免了將老年代也加入gc root的掃描范圍。
hotspot使用卡表的方式實現了記憶集。
卡表是一個字節數組實現的對象,在老年代中存在“卡頁”,記錄了引用的內存地址。年輕代中存着“卡表”是一個字節數組,類似“0101”的數據,其中1代表老年代中的卡頁關聯着年輕代,即它是Dirty的。這個時候就需要去找相應索引的卡頁。
hotspot中使用了寫屏障來維護卡表狀態。
G1收集器中在每個Region都有一個卡表,CMS收集器中則是年輕代有卡表,老年代有卡頁