學(xué)習(xí)java的同學(xué)注意了!!! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:183993990 我們一起學(xué)Java!
G1 GC,全稱Garbage-First Garbage Collector,通過-XX:+UseG1GC參數(shù)來啟用,作為體驗(yàn)版隨著JDK 6u14版本面世,在JDK 7u4版本發(fā)行時(shí)被正式推出,相信熟悉JVM的同學(xué)們都不會(huì)對(duì)它感到陌生。在JDK 9中,G1被提議設(shè)置為默認(rèn)垃圾收集器(JEP 248)。在官網(wǎng)中,是這樣描述G1的:
The Garbage-First (G1) collector is a server-style garbage collector, targeted for multi-PRocessor machines with large memories. It meets garbage collection (GC) pause time goals with a high probability, while achieving high throughput. The G1 garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is designed for applications that:
Can Operate concurrently with applications threads like the CMS collector.Compact free space without lengthy GC induced pause times.Need more predictable GC pause durations.Do not want to sacrifice a lot of throughput performance.Do not require a much larger Java heap.從官網(wǎng)的描述中,我們知道G1是一種服務(wù)器端的垃圾收集器,應(yīng)用在多處理器和大容量內(nèi)存環(huán)境中,在實(shí)現(xiàn)高吞吐量的同時(shí),盡可能的滿足垃圾收集暫停時(shí)間的要求。它是專門針對(duì)以下應(yīng)用場景設(shè)計(jì)的:
像CMS收集器一樣,能與應(yīng)用程序線程并發(fā)執(zhí)行。整理空閑空間更快。需要GC停頓時(shí)間更好預(yù)測(cè)。不希望犧牲大量的吞吐性能。不需要更大的Java Heap。G1收集器的設(shè)計(jì)目標(biāo)是取代CMS收集器,它同CMS相比,在以下方面表現(xiàn)的更出色:
G1是一個(gè)有整理內(nèi)存過程的垃圾收集器,不會(huì)產(chǎn)生很多內(nèi)存碎片。G1的Stop The World(STW)更可控,G1在停頓時(shí)間上添加了預(yù)測(cè)機(jī)制,用戶可以指定期望停頓時(shí)間。有了以上的特性,難怪有人說它是一款駕馭一切的垃圾收集器(G1: One Garbage Collector To Rule Them All)。本文帶大家來了解一下G1 GC的一些關(guān)鍵技術(shù),為能正確的使用它,做好理論基礎(chǔ)的鋪墊。
G1中幾個(gè)重要概念
在G1的實(shí)現(xiàn)過程中,引入了一些新的概念,對(duì)于實(shí)現(xiàn)高吞吐、沒有內(nèi)存碎片、收集時(shí)間可控等功能起到了關(guān)鍵作用。下面我們就一起看一下G1中的這幾個(gè)重要概念。
Region
傳統(tǒng)的GC收集器將連續(xù)的內(nèi)存空間劃分為新生代、老年代和永久代(JDK 8去除了永久代,引入了元空間Metaspace),這種劃分的特點(diǎn)是各代的存儲(chǔ)地址(邏輯地址,下同)是連續(xù)的。如下圖所示:
而G1的各代存儲(chǔ)地址是不連續(xù)的,每一代都使用了n個(gè)不連續(xù)的大小相同的Region,每個(gè)Region占有一塊連續(xù)的虛擬內(nèi)存地址。如下圖所示:
在上圖中,我們注意到還有一些Region標(biāo)明了H,它代表Humongous,這表示這些Region存儲(chǔ)的是巨大對(duì)象(humongous object,H-obj),即大小大于等于region一半的對(duì)象。H-obj有如下幾個(gè)特征:
H-obj直接分配到了old gen,防止了反復(fù)拷貝移動(dòng)。H-obj在global concurrent marking階段的cleanup 和 full GC階段回收。在分配H-obj之前先檢查是否超過 initiating heap occupancy percent和the marking threshold, 如果超過的話,就啟動(dòng)global concurrent marking,為的是提早回收,防止 evacuation failures 和 full GC。為了減少連續(xù)H-objs分配對(duì)GC的影響,需要把大對(duì)象變?yōu)槠胀ǖ膶?duì)象,建議增大Region size。
一個(gè)Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設(shè)定,取值范圍從1M到32M,且是2的指數(shù)。如果不設(shè)定,那么G1會(huì)根據(jù)Heap大小自動(dòng)決定。相關(guān)的設(shè)置代碼如下:
// share/vm/gc_implementation/g1/heapRegion.cpp// Minimum region size; we won't go lower than that.// We might want to decrease this in the future, to deal with small// heaps a bit more efficiently.#define MIN_REGION_SIZE ( 1024 * 1024 )// Maximum region size; we don't go higher than that. There's a good// reason for having an upper bound. We don't want regions to get too// large, otherwise cleanup's effectiveness would decrease as there// will be fewer opportunities to find totally empty regions after// marking.#define MAX_REGION_SIZE ( 32 * 1024 * 1024 )// The automatic region size calculation will try to have around this// many regions in the heap (based on the min heap size).#define TARGET_REGION_NUMBER 2048void HeapRegion::setup_heap_region_size(size_t initial_heap_size, size_t max_heap_size) { uintx region_size = G1HeapRegionSize; if (FLAG_IS_DEFAULT(G1HeapRegionSize)) { size_t average_heap_size = (initial_heap_size + max_heap_size) / 2; region_size = MAX2(average_heap_size / TARGET_REGION_NUMBER, (uintx) MIN_REGION_SIZE); } int region_size_log = log2_long((jlong) region_size); // Recalculate the region size to make sure it's a power of // 2. This means that region_size is the largest power of 2 that's // <= what we've calculated so far. region_size = ((uintx)1 << region_size_log); // Now make sure that we don't go over or under our limits. if (region_size < MIN_REGION_SIZE) { region_size = MIN_REGION_SIZE; } else if (region_size > MAX_REGION_SIZE) { region_size = MAX_REGION_SIZE; }}
SATB
全稱是Snapshot-At-The-Beginning,由字面理解,是GC開始時(shí)活著的對(duì)象的一個(gè)快照。它是通過Root Tracing得到的,作用是維持并發(fā)GC的正確性。那么它是怎么維持并發(fā)GC的正確性的呢?根據(jù)三色標(biāo)記算法,我們知道對(duì)象存在三種狀態(tài):
白:對(duì)象沒有被標(biāo)記到,標(biāo)記階段結(jié)束后,會(huì)被當(dāng)做垃圾回收掉。灰:對(duì)象被標(biāo)記了,但是它的field還沒有被標(biāo)記或標(biāo)記完。黑:對(duì)象被標(biāo)記了,且它的所有field也被標(biāo)記完了。由于并發(fā)階段的存在,Mutator和Garbage Collector線程同時(shí)對(duì)對(duì)象進(jìn)行修改,就會(huì)出現(xiàn)白對(duì)象漏標(biāo)的情況,這種情況發(fā)生的前提是:
Mutator賦予一個(gè)黑對(duì)象該白對(duì)象的引用。Mutator刪除了所有從灰對(duì)象到該白對(duì)象的直接或者間接引用。對(duì)于第一個(gè)條件,在并發(fā)標(biāo)記階段,如果該白對(duì)象是new出來的,并沒有被灰對(duì)象持有,那么它會(huì)不會(huì)被漏標(biāo)呢?Region中有兩個(gè)top-at-mark-start(TAMS)指針,分別為prevTAMS和nextTAMS。在TAMS以上的對(duì)象是新分配的,這是一種隱式的標(biāo)記。對(duì)于在GC時(shí)已經(jīng)存在的白對(duì)象,如果它是活著的,它必然會(huì)被另一個(gè)對(duì)象引用,即條件二中的灰對(duì)象。如果灰對(duì)象到白對(duì)象的直接引用或者間接引用被替換了,或者刪除了,白對(duì)象就會(huì)被漏標(biāo),從而導(dǎo)致被回收掉,這是非常嚴(yán)重的錯(cuò)誤,所以SATB破壞了第二個(gè)條件。也就是說,一個(gè)對(duì)象的引用被替換時(shí),可以通過write barrier 將舊引用記錄下來。
// share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.hpp// This notes that we don't need to access any BarrierSet data// structures, so this can be called from a static context.template <class T> static void write_ref_field_pre_static(T* field, oop newVal) { T heap_oop = oopDesc::load_heap_oop(field); if (!oopDesc::is_null(heap_oop)) { enqueue(oopDesc::decode_heap_oop(heap_oop)); }}// share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.cppvoid G1SATBCardTableModRefBS::enqueue(oop pre_val) { // Nulls should have been already filtered. assert(pre_val->is_oop(true), "Error"); if (!JavaThread::satb_mark_queue_set().is_active()) return; Thread* thr = Thread::current(); if (thr->is_Java_thread()) { JavaThread* jt = (JavaThread*)thr; jt->satb_mark_queue().enqueue(pre_val); } else { MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag); JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val); }}
SATB也是有副作用的,如果被替換的白對(duì)象就是要被收集的垃圾,這次的標(biāo)記會(huì)讓它躲過GC,這就是float garbage。因?yàn)镾ATB的做法精度比較低,所以造成的float garbage也會(huì)比較多。
RSet
全稱是Remembered Set,是輔助GC過程的一種結(jié)構(gòu),典型的空間換時(shí)間工具,和Card Table有些類似。還有一種數(shù)據(jù)結(jié)構(gòu)也是輔助GC的:Collection Set(CSet),它記錄了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的時(shí)候,對(duì)于old->young和old->old的跨代對(duì)象引用,只要掃描對(duì)應(yīng)的CSet中的RSet即可。邏輯上說每個(gè)Region都有一個(gè)RSet,RSet記錄了其他Region中的對(duì)象引用本Region中對(duì)象的關(guān)系,屬于points-into結(jié)構(gòu)(誰引用了我的對(duì)象)。而Card Table則是一種points-out(我引用了誰的對(duì)象)的結(jié)構(gòu),每個(gè)Card 覆蓋一定范圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎(chǔ)上實(shí)現(xiàn)的:每個(gè)Region會(huì)記錄下別的Region有指向自己的指針,并標(biāo)記這些指針分別在哪些Card的范圍內(nèi)。 這個(gè)RSet其實(shí)是一個(gè)Hash Table,Key是別的Region的起始地址,Value是一個(gè)集合,里面的元素是Card Table的Index。
下圖表示了RSet、Card和Region的關(guān)系(出處):
上圖中有三個(gè)Region,每個(gè)Region被分成了多個(gè)Card,在不同Region中的Card會(huì)相互引用,Region1中的Card中的對(duì)象引用了Region2中的Card中的對(duì)象,藍(lán)色實(shí)線表示的就是points-out的關(guān)系,而在Region2的RSet中,記錄了Region1的Card,即紅色虛線表示的關(guān)系,這就是points-into。而維系RSet中的引用關(guān)系靠post-write barrier和Concurrent refinement threads來維護(hù),操作偽代碼如下(出處):
void oop_field_store(oop* field, oop new_value) { pre_write_barrier(field); // pre-write barrier: for maintaining SATB invariant *field = new_value; // the actual store post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference}
post-write barrier記錄了跨Region的引用更新,更新日志緩沖區(qū)則記錄了那些包含更新引用的Cards。一旦緩沖區(qū)滿了,Post-write barrier就停止服務(wù)了,會(huì)由Concurrent refinement threads處理這些緩沖區(qū)日志。RSet究竟是怎么輔助GC的呢?在做YGC的時(shí)候,只需要選定young generation region的RSet作為根集,這些RSet記錄了old->young的跨代引用,避免了掃描整個(gè)old generation。 而mixed gc的時(shí)候,old generation中記錄了old->old的RSet,young->old的引用由掃描全部young generation region得到,這樣也不用掃描全部old generation region。所以RSet的引入大大減少了GC的工作量。
Pause Prediction Model
Pause Prediction Model 即停頓預(yù)測(cè)模型。它在G1中的作用是:
G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.
G1 GC是一個(gè)響應(yīng)時(shí)間優(yōu)先的GC算法,它與CMS最大的不同是,用戶可以設(shè)定整個(gè)GC過程的期望停頓時(shí)間,參數(shù)-XX:MaxGCPauseMillis指定一個(gè)G1收集過程目標(biāo)停頓時(shí)間,默認(rèn)值200ms,不過它不是硬性條件,只是期望值。那么G1怎么滿足用戶的期望呢?就需要這個(gè)停頓預(yù)測(cè)模型了。G1根據(jù)這個(gè)模型統(tǒng)計(jì)計(jì)算出來的歷史數(shù)據(jù)來預(yù)測(cè)本次收集需要選擇的Region數(shù)量,從而盡量滿足用戶設(shè)定的目標(biāo)停頓時(shí)間。停頓預(yù)測(cè)模型是以衰減標(biāo)準(zhǔn)偏差為理論基礎(chǔ)實(shí)現(xiàn)的:
// share/vm/gc_implementation/g1/g1CollectorPolicy.hppdouble get_new_prediction(TruncatedSeq* seq) { return MAX2(seq->davg() + sigma() * seq->dsd(), seq->davg() * confidence_factor(seq->num()));}
在這個(gè)預(yù)測(cè)計(jì)算公式中:davg表示衰減均值,sigma()返回一個(gè)系數(shù),表示信賴度,dsd表示衰減標(biāo)準(zhǔn)偏差,confidence_factor表示可信度相關(guān)系數(shù)。而方法的參數(shù)TruncateSeq,顧名思義,是一個(gè)截?cái)嗟男蛄校桓櫫诵蛄兄械淖钚碌膎個(gè)元素。
在G1 GC過程中,每個(gè)可測(cè)量的步驟花費(fèi)的時(shí)間都會(huì)記錄到TruncateSeq(繼承了AbsSeq)中,用來計(jì)算衰減均值、衰減變量,衰減標(biāo)準(zhǔn)偏差等:
// src/share/vm/utilities/numberSeq.cppvoid AbsSeq::add(double val) { if (_num == 0) { // if the sequence is empty, the davg is the same as the value _davg = val; // and the variance is 0 _dvariance = 0.0; } else { // otherwise, calculate both _davg = (1.0 - _alpha) * val + _alpha * _davg; double diff = val - _davg; _dvariance = (1.0 - _alpha) * diff * diff + _alpha * _dvariance; }}
比如要預(yù)測(cè)一次GC過程中,RSet的更新時(shí)間,這個(gè)操作主要是將Dirty Card加入到RSet中,具體原理參考前面的RSet。每個(gè)Dirty Card的時(shí)間花費(fèi)通過_cost_per_card_ms_seq來記錄,具體預(yù)測(cè)代碼如下:
// share/vm/gc_implementation/g1/g1CollectorPolicy.hpp double predict_rs_update_time_ms(size_t pending_cards) { return (double) pending_cards * predict_cost_per_card_ms(); } double predict_cost_per_card_ms() { return get_new_prediction(_cost_per_card_ms_seq); }
get_new_prediction就是我們開頭說的方法,現(xiàn)在大家應(yīng)該基本明白停頓預(yù)測(cè)模型的實(shí)現(xiàn)原理了。
GC過程
講完了一些基本概念,下面我們就來看看G1的GC過程是怎樣的。
G1 GC模式
G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是完全Stop The World的。
Young GC:選定所有年輕代里的Region。通過控制年輕代的region個(gè)數(shù),即年輕代內(nèi)存大小,來控制young GC的時(shí)間開銷。Mixed GC:選定所有年輕代里的Region,外加根據(jù)global concurrent marking統(tǒng)計(jì)得出收集收益高的若干老年代Region。在用戶指定的開銷目標(biāo)范圍內(nèi)盡可能選擇收益高的老年代Region。由上面的描述可知,Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC實(shí)在無法跟上程序分配內(nèi)存的速度,導(dǎo)致老年代填滿無法繼續(xù)進(jìn)行Mixed GC,就會(huì)使用serial old GC(full GC)來收集整個(gè)GC heap。所以我們可以知道,G1是不提供full GC的。
上文中,多次提到了global concurrent marking,它的執(zhí)行過程類似CMS,但是不同的是,在G1 GC中,它主要是為Mixed GC提供標(biāo)記服務(wù)的,并不是一次GC過程的一個(gè)必須環(huán)節(jié)。global concurrent marking的執(zhí)行過程分為四個(gè)步驟:
初始標(biāo)記(initial mark,STW)。它標(biāo)記了從GC Root開始直接可達(dá)的對(duì)象。并發(fā)標(biāo)記(Concurrent Marking)。這個(gè)階段從GC Root開始對(duì)heap中的對(duì)象標(biāo)記,標(biāo)記線程與應(yīng)用程序線程并行執(zhí)行,并且收集各個(gè)Region的存活對(duì)象信息。最終標(biāo)記(Remark,STW)。標(biāo)記那些在并發(fā)標(biāo)記階段發(fā)生變化的對(duì)象,將被回收。清除垃圾(Cleanup)。清除空Region(沒有存活對(duì)象的),加入到free list。第一階段initial mark是共用了Young GC的暫停,這是因?yàn)樗麄兛梢詮?fù)用root scan操作,所以可以說global concurrent marking是伴隨Young GC而發(fā)生的。第四階段Cleanup只是回收了沒有存活對(duì)象的Region,所以它并不需要STW。
Young GC發(fā)生的時(shí)機(jī)大家都知道,那什么時(shí)候發(fā)生Mixed GC呢?其實(shí)是由一些參數(shù)控制著的,另外也控制著哪些老年代Region會(huì)被選入CSet。
G1HeapWastePercent:在global concurrent marking結(jié)束之后,我們可以知道old gen regions中有多少空間要被回收,在每次YGC之后和再次發(fā)生Mixed GC之前,會(huì)檢查垃圾占比是否達(dá)到此參數(shù),只有達(dá)到了,下次才會(huì)發(fā)生Mixed GC。G1MixedGCLiveThresholdPercent:old generation region中的存活對(duì)象的占比,只有在此參數(shù)之下,才會(huì)被選入CSet。G1MixedGCCountTarget:一次global concurrent marking之后,最多執(zhí)行Mixed GC的次數(shù)。G1OldCSetRegionThresholdPercent:一次Mixed GC中能被選入CSet的最多old generation region數(shù)量。除了以上的參數(shù),G1 GC相關(guān)的其他主要的參數(shù)有:
參數(shù) 含義 -XX:G1HeapRegionSize=n 設(shè)置Region大小,并非最終值 -XX:MaxGCPauseMillis 設(shè)置G1收集過程目標(biāo)時(shí)間,默認(rèn)值200ms,不是硬性條件 -XX:G1NewSizePercent 新生代最小值,默認(rèn)值5% -XX:G1MaxNewSizePercent 新生代最大值,默認(rèn)值60% -XX:ParallelGCThreads STW期間,并行GC線程數(shù) -XX:ConcGCThreads=n 并發(fā)標(biāo)記階段,并行執(zhí)行的線程數(shù) -XX:InitiatingHeapOccupancyPercent 設(shè)置觸發(fā)標(biāo)記周期的 Java 堆占用率閾值。默認(rèn)值是45%。這里的java堆占比指的是non_young_capacity_bytes,包括old+humongous GC日志
G1收集器的日志與其他收集器有很大不同,源于G1獨(dú)立的體系架構(gòu)和數(shù)據(jù)結(jié)構(gòu),下面這兩段日志來源于美團(tuán)點(diǎn)評(píng)的CRM系統(tǒng)線上生產(chǎn)環(huán)境。
Young GC日志
我們先來看看Young GC的日志:
{Heap before GC invocations=12 (full 1): garbage-first heap total 3145728K, used 336645K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) region size 1024K, 172 young (176128K), 13 survivors (13312K) Metaspace used 29944K, capacity 30196K, committed 30464K, reserved 1077248K class space used 3391K, capacity 3480K, committed 3584K, reserved 1048576K2014-11-14T17:57:23.654+0800: 27.884: [GC pause (G1 Evacuation Pause) (young)Desired survivor size 11534336 bytes, new threshold 15 (max 15)- age 1: 5011600 bytes, 5011600 total 27.884: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms] 27.884: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms] 27.884: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms], 0.0158389 secs] [Parallel Time: 8.1 ms, GC Workers: 4] [GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1] [Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1] [Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4] [Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11] [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3] [Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6] [Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4] [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4] [GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2] [GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3] [Code Root Fixup: 0.5 ms] [Code Root Migration: 1.3 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.2 ms] [Other: 5.8 ms] [Choose CSet: 0.0 ms] [Ref Proc: 5.0 ms] [Ref Enq: 0.1 ms] [Redirty Cards: 0.0 ms] [Free CSet: 0.2 ms] [Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)]Heap after GC invocations=13 (full 1): garbage-first heap total 3145728K, used 171269K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) region size 1024K, 11 young (11264K), 11 survivors (11264K) Metaspace used 29944K, capacity 30196K, committed 30464K, reserved 1077248K class space used 3391K, capacity 3480K, committed 3584K, reserved 1048576K} [Times: user=0.05 sys=0.01, real=0.02 secs]
每個(gè)過程的作用如下:
garbage-first heap total 3145728K, used 336645K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)這行表示使用了G1垃圾收集器,total heap 3145728K,使用了336645K。region size 1024K, 172 young (176128K), 13 survivors (13312K)Region大小為1M,青年代占用了172個(gè)(共176128K),幸存區(qū)占用了13個(gè)(共13312K)。Metaspace used 29944K, capacity 30196K, committed 30464K, reserved 1077248Kclass space used 3391K, capacity 3480K, committed 3584K, reserved 1048576Kjava 8的新特性,去掉永久區(qū),添加了元數(shù)據(jù)區(qū),這塊不是本文重點(diǎn),不再贅述。需要注意的是,之所以有committed和reserved,是因?yàn)闆]有設(shè)置MetaspaceSize=MaxMetaspaceSize。[GC pause (G1 Evacuation Pause) (young)GC原因,新生代minor GC。[G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms]發(fā)生minor GC和full GC時(shí),所有相關(guān)region都是要回收的。而發(fā)生并發(fā)GC時(shí),會(huì)根據(jù)目標(biāo)停頓時(shí)間動(dòng)態(tài)選擇部分垃圾對(duì)并多的Region回收,這一步就是選擇Region。_pending_cards是關(guān)于RSet的Card Table。predicted base time是預(yù)測(cè)的掃描card table時(shí)間。[G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms]這一步是添加Region到collection set,新生代一共159個(gè)Region,13個(gè)幸存區(qū)Region,這也和之前的(172 young (176128K), 13 survivors (13312K))吻合。預(yù)計(jì)收集時(shí)間是44.09 ms。[G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms]這一步是對(duì)上面兩步的總結(jié)。預(yù)計(jì)總收集時(shí)間79.34ms。[Parallel Time: 8.1 ms, GC Workers: 4]由于收集過程是多線程并行(并發(fā))進(jìn)行,這里是4個(gè)線程,總共耗時(shí)8.1ms(wall clock time)[GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1]收集線程開始的時(shí)間,使用的是相對(duì)時(shí)間,Min是最早開始時(shí)間,Avg是平均開始時(shí)間,Max是最晚開始時(shí)間,Diff是Max-Min(此處的0.1貌似有問題)[Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1]掃描Roots花費(fèi)的時(shí)間,Sum表示total cpu time,下同。[Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4] [Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11]Update RS (ms)是每個(gè)線程花費(fèi)在更新Remembered Set上的時(shí)間。[Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]掃描CS中的region對(duì)應(yīng)的RSet,因?yàn)镽Set是points-into,所以這樣實(shí)現(xiàn)避免了掃描old generadion region,但是會(huì)產(chǎn)生float garbage。[Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]掃描code root耗時(shí)。code root指的是經(jīng)過JIT編譯后的代碼里,引用了heap中的對(duì)象。引用關(guān)系保存在RSet中。[Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4]拷貝活的對(duì)象到新region的耗時(shí)。[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]線程結(jié)束,在結(jié)束前,它會(huì)檢查其他線程是否還有未掃描完的引用,如果有,則"偷"過來,完成后再申請(qǐng)結(jié)束,這個(gè)時(shí)間是線程之前互相同步所花費(fèi)的時(shí)間。[GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4]花費(fèi)在其他工作上(未列出)的時(shí)間。[GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2]每個(gè)線程花費(fèi)的時(shí)間和。[GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3]每個(gè)線程結(jié)束的時(shí)間。[Code Root Fixup: 0.5 ms]用來將code root修正到正確的evacuate之后的對(duì)象位置所花費(fèi)的時(shí)間。[Code Root Migration: 1.3 ms]更新code root 引用的耗時(shí),code root中的引用因?yàn)閷?duì)象的evacuation而需要更新。[Code Root Purge: 0.0 ms]清除code root的耗時(shí),code root中的引用已經(jīng)失效,不再指向Region中的對(duì)象,所以需要被清除。[Clear CT: 0.2 ms]清除card table的耗時(shí)。[Other: 5.8 ms][Choose CSet: 0.0 ms][Ref Proc: 5.0 ms][Ref Enq: 0.1 ms][Redirty Cards: 0.0 ms][Free CSet: 0.2 ms]其他事項(xiàng)共耗時(shí)5.8ms,其他事項(xiàng)包括選擇CSet,處理已用對(duì)象,引用入ReferenceQueues,釋放CSet中的region到free list。[Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)]新生代清空了,下次擴(kuò)容到301MB。global concurrent marking 日志
對(duì)于global concurrent marking過程,它的日志如下所示:
66955.252: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: occupancy higher than threshold, occupancy: 1449132032 bytes, allocation request: 579608 bytes, threshold: 1449551430 bytes (45.00 %), source: concurrent humongous allocation]2014-12-10T11:13:09.532+0800: 66955.252: Application time: 2.5750418 seconds 66955.259: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: requested by GC cause, GC cause: G1 Humongous Allocation]{Heap before GC invocations=1874 (full 4): garbage-first heap total 3145728K, used 1281786K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) region size 1024K, 171 young (175104K), 27 survivors (27648K) Metaspace used 116681K, capacity 137645K, committed 137984K, reserved 1171456K class space used 13082K, capacity 16290K, committed 16384K, reserved 1048576K 66955.259: [G1Ergonomics (Concurrent Cycles) initiate concurrent cycle, reason: concurrent cycle initiation requested]2014-12-10T11:13:09.539+0800: 66955.259: [GC pause (G1 Humongous Allocation) (young) (initial-mark)…….2014-12-10T11:13:09.597+0800: 66955.317: [GC concurrent-root-region-scan-start]2014-12-10T11:13:09.597+0800: 66955.318: Total time for which application threads were stopped: 0.0655753 seconds2014-12-10T11:13:09.610+0800: 66955.330: Application time: 0.0127071 seconds2014-12-10T11:13:09.614+0800: 66955.335: Total time for which application threads were stopped: 0.0043882 seconds2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-root-region-scan-end, 0.0281351 secs]2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-mark-start]2014-12-10T11:13:09.645+0800: 66955.365: Application time: 0.0306801 seconds2014-12-10T11:13:09.651+0800: 66955.371: Total time for which application threads were stopped: 0.0061326 seconds2014-12-10T11:13:10.212+0800: 66955.933: [GC concurrent-mark-end, 0.5871129 secs]2014-12-10T11:13:10.212+0800: 66955.933: Application time: 0.5613792 seconds2014-12-10T11:13:10.215+0800: 66955.935: [GC remark 66955.936: [GC ref-proc, 0.0235275 secs], 0.0320865 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]2014-12-10T11:13:10.247+0800: 66955.968: Total time for which application threads were stopped: 0.0350098 seconds2014-12-10T11:13:10.248+0800: 66955.968: Application time: 0.0001691 seconds2014-12-10T11:13:10.250+0800: 66955.970: [GC cleanup 1178M->632M(3072M), 0.0060632 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]2014-12-10T11:13:10.256+0800: 66955.977: Total time for which application threads were stopped: 0.0088462 seconds2014-12-10T11:13:10.257+0800: 66955.977: [GC concurrent-cleanup-start]2014-12-10T11:13:10.259+0800: 66955.979: [GC concurrent-cleanup-end, 0.0024743 secs
這次發(fā)生global concurrent marking的原因是:humongous allocation,上面提過在巨大對(duì)象分配之前,會(huì)檢測(cè)到old generation 使用占比是否超過了 initiating heap occupancy percent(45%),因?yàn)?449132032(used)+ 579608(allocation request:) > 1449551430(threshold),所以觸發(fā)了本次global concurrent marking。對(duì)于具體執(zhí)行過程,上面的表格已經(jīng)詳細(xì)講解了。值得注意的是上文中所說的initial mark往往伴隨著一次YGC,在日志中也有體現(xiàn):GC pause (G1 Humongous Allocation) (young) (initial-mark)。
后記
因?yàn)槠年P(guān)系,也受限于能力水平,本文只是簡單了介紹了G1 GC的基本原理,很多細(xì)節(jié)沒有涉及到,所以說只能算是為研究和使用它的同學(xué)打開了一扇門。一個(gè)日本人專門寫了一本書《徹底解剖「G1GC」 アルゴリズ》詳細(xì)的介紹了G1 GC,這本書也被作者放到了GitHub上,詳見參考文獻(xiàn)5。另外,莫樞在這方面也研究的比較多,讀者可以去高級(jí)語言虛擬機(jī)論壇向他請(qǐng)教,本文的很多內(nèi)容也是我在此論壇上請(qǐng)教過后整理的。總而言之,G1是一款非常優(yōu)秀的垃圾收集器,盡管還有些不完美(預(yù)測(cè)模型還不夠智能),但是希望有更多的同學(xué)來使用它,研究它,提出好的建議,讓它變的更加完善。
學(xué)習(xí)Java的同學(xué)注意了!!! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:183993990 我們一起學(xué)Java!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注