- JVM G1源碼分析和調優(yōu)
- 彭成寒
- 2264字
- 2019-04-22 18:14:52
2.1 分區(qū)
分區(qū)(Heap Region,HR)或稱堆分區(qū),是G1堆和操作系統(tǒng)交互的最小管理單位。G1的分區(qū)類型(HeapRegionType)大致可以分為四類:
·自由分區(qū)(Free Heap Region,F(xiàn)HR)
·新生代分區(qū)(Young Heap Region,YHR)
·大對象分區(qū)(Humongous Heap Region,HHR)
·老生代分區(qū)(Old Heap Region,OHR)
其中新生代分區(qū)又可以分為Eden和Survivor;大對象分區(qū)又可以分為:大對象頭分區(qū)和大對象連續(xù)分區(qū)。
每一個分區(qū)都對應一個分區(qū)類型,在代碼中常見的is_young、is_old、is_houmongous等判斷分區(qū)類型的函數(shù)都是基于上述的分區(qū)類型實現(xiàn),關于分區(qū)類型代碼如下所示:
hotspot/src/share/vm/gc_implementation/g1/heapRegionType.hpp // 0000 0 [ 0] Free // // 0001 0 Young Mask // 0001 0 [ 2] Eden // 0001 1 [ 3] Survivor // // 0010 0 Humongous Mask // 0010 0 [ 4] Humongous Starts // 0010 1 [ 5] Humongous Continues // // 01000 [ 8] Old
在G1中每個分區(qū)的大小都是相同的。該如何設置HR的大小?設置HR的大小有哪些考慮?
HR的大小直接影響分配和垃圾回收效率。如果過大,一個HR可以存放多個對象,分配效率高,但是回收的時候花費時間過長;如果太小則導致分配效率低下。為了達到分配效率和清理效率的平衡,HR有一個上限值和下限值,目前上限是32MB,下限是1MB(為了適應更小的內存分配,下限可能會被修改,在目前的版本中HR的大小只能為1MB、2MB、4MB、8MB、16MB和32MB),默認情況下,整個堆空間分為2048個HR(該值可以自動根據(jù)最小的堆分區(qū)大小計算得出)。HR大小可由以下方式確定:
·可以通過參數(shù)G1HeapRegionSize來指定大小,這個參數(shù)的默認值為0。
·啟發(fā)式推斷,即在不指定HR大小的時候,由G1啟發(fā)式地推斷HR大小。
HR啟發(fā)式推斷根據(jù)堆空間的最大值和最小值以及HR個數(shù)進行推斷,設置Initial HeapSize(默認為0)等價于設置Xms,設置MaxHeapSize(默認為96MB)等價于設置Xmx。堆分區(qū)默認大小的計算方式在HeapRegion.cpp中的setup_heap_region_size(),代碼如下所示:
hotspot/src/share/vm/gc_implementation/g1/heapRegion.cpp void HeapRegion::setup_heap_region_size(...) { /*判斷是否是設置過堆分區(qū)大小,如果有則使用;沒有,則根據(jù)初始內存和最大分配內存,獲得平均值,并根據(jù)HR的個數(shù)得到分區(qū)的大小,和分區(qū)的下限比較,取兩者的最大值。*/ 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 / HeapRegionBounds::target_number(), (uintx) HeapRegionBounds::min_size()); } // 對region_size按2的冪次對齊,并且保證其落在上下限范圍內 int region_size_log = log2_long((jlong) region_size); region_size = ((uintx)1 << region_size_log); // 確保region_size落在[1MB,32MB]之間 if (region_size < HeapRegionBounds::min_size()) { region_size = HeapRegionBounds::min_size(); } else if (region_size > HeapRegionBounds::max_size()) { region_size = HeapRegionBounds::max_size(); } // 根據(jù)region_size計算一些變量,如卡表大小 region_size_log = log2_long((jlong) region_size); LogOfHRGrainBytes = region_size_log; LogOfHRGrainWords = LogOfHRGrainBytes - LogHeapWordSize; GrainBytes = (size_t)region_size; GrainWords = GrainBytes >> LogHeapWordSize; CardsPerRegion = GrainBytes >> CardTableModRefBS::card_shift; }
按照默認值計算,G1可以管理的最大內存為2048×32MB=64GB。假設設置xms=32G,xmx=128G,則每個堆分區(qū)的大小為32M,分區(qū)個數(shù)動態(tài)變化范圍從1024到4096個。
G1中大對象不使用新生代空間,直接進入老生代,那么多大的對象能稱為大對象?簡單來說是region_size的一半。
新生代大小
新生代大小指的是新生代內存空間的大小,前面提到G1中新生代大小按分區(qū)組織,即首先計算整個新生代的大小,然后根據(jù)上一節(jié)中的計算方法計算得到分區(qū)大小,兩者相除得到需要多少個分區(qū)。G1中與新生代大小相關的參數(shù)設置和其他GC算法類似,G1中還增加了兩個參數(shù)G1MaxNewSizePercent和G1NewSizePercent用于控制新生代的大小,整體邏輯如下:
·如果設置新生代最大值(MaxNewSize)和最小值(NewSize),可以根據(jù)這些值計算新生代包含的最大的分區(qū)和最小的分區(qū);注意Xmn等價于設置了MaxNewSize和NewSize,且NewSize=MaxNewSize。
·如果既設置了最大值或者最小值,又設置了NewRatio,則忽略NewRatio。
·如果沒有設置新生代最大值和最小值,但是設置了NewRatio,則新生代的最大值和最小值是相同的,都是整個堆空間/(NewRatio+1)。
·如果沒有設置新生代最大值和最小值,或者只設置了最大值和最小值中的一個,那么G1將根據(jù)參數(shù)G1MaxNewSizePercent(默認值為60)和G1NewSizePercent(默認值為5)占整個堆空間的比例來計算最大值和最小值。
值得注意的是,如果G1推斷出最大值和最小值相等,則說明新生代不會動態(tài)變化。不會動態(tài)變化意味著G1在后續(xù)對新生代垃圾回收的時候可能不能滿足期望停頓的時間,具體內容將在后文繼續(xù)介紹。新生代大小相關的代碼如下所示:
hotspot/src/share/vm/gc_implementation/g1/g1CollectorPolicy.cpp // 初始化新生代大小參數(shù),根據(jù)不同的JVM參數(shù)判斷計算新生代大小,供后續(xù)使用 G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), _adaptive_ size(true), _min_desired_young_length(0), _max_desired_young_length(0) { // 如果設置NewRatio且同時設置NewSize或MaxNewSize的情況下,則NewRatio被忽略 if (FLAG_IS_CMDLINE(NewRatio)) { if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) { warning("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio"); } else { _sizer_kind = SizerNewRatio; _adaptive_size = false; return; } } // 參數(shù)傳遞有問題,最小值大于最大值 if (NewSize > MaxNewSize) { if (FLAG_IS_CMDLINE(MaxNewSize)) { warning("…”); } MaxNewSize = NewSize; } // 根據(jù)參數(shù)計算分區(qū)的個數(shù) if (FLAG_IS_CMDLINE(NewSize)) { _min_desired_young_length = MAX2((uint) (NewSize / HeapRegion:: GrainBytes), 1U); if (FLAG_IS_CMDLINE(MaxNewSize)) { _max_desired_young_length = MAX2((uint) (MaxNewSize / HeapRegion:: GrainBytes), 1U); _sizer_kind = SizerMaxAndNewSize; _adaptive_size = _min_desired_young_length == _max_desired_young_length; } else { _sizer_kind = SizerNewSizeOnly; } } else if (FLAG_IS_CMDLINE(MaxNewSize)) { _max_desired_young_length = MAX2((uint) (MaxNewSize / HeapRegion:: GrainBytes), 1U); _sizer_kind = SizerMaxNewSizeOnly; } } // 使用G1NewSizePercent來計算新生代的最小值 uint G1YoungGenSizer::calculate_default_min_length(uint new_number_of_heap_ regions) { uint default_value = (new_number_of_heap_regions * G1NewSizePercent) / 100; return MAX2(1U, default_value); } // 使用G1MaxNewSizePercent來計算新生代的最大值 uint G1YoungGenSizer::calculate_default_max_length(uint new_number_of_heap_ regions) { uint default_value = (new_number_of_heap_regions * G1MaxNewSizePercent) / 100; return MAX2(1U, default_value); } /*這里根據(jù)不同的參數(shù)輸入來計算大小。recalculate_min_max_young_length在初始化時被調用,在堆空間改變時也會被調用。*/ void G1YoungGenSizer::recalculate_min_max_young_length(uint number_of_heap_ regions, uint* min_young_length, uint* max_young_length) { assert(number_of_heap_regions > 0, "Heap must be initialized"); switch (_sizer_kind) { case SizerDefaults: *min_young_length = calculate_default_min_length(number_of_heap_regions); *max_young_length = calculate_default_max_length(number_of_heap_regions); break; case SizerNewSizeOnly: *max_young_length = calculate_default_max_length(number_of_heap_regions); *max_young_length = MAX2(*min_young_length, *max_young_length); break; case SizerMaxNewSizeOnly: *min_young_length = calculate_default_min_length(number_of_heap_regions); *min_young_length = MIN2(*min_young_length, *max_young_length); break; case SizerMaxAndNewSize: // Do nothing. Values set on the command line, don't update them at runtime. break; case SizerNewRatio: *min_young_length = number_of_heap_regions / (NewRatio + 1); *max_young_length = *min_young_length; break; default: ShouldNotReachHere(); } }
如果G1是啟發(fā)式推斷新生代的大小,那么當新生代變化時該如何實現(xiàn)?簡單地說,使用一個分區(qū)列表,擴張時如果有空閑的分區(qū)列表則可以直接把空閑分區(qū)加入到新生代分區(qū)列表中,如果沒有的話則分配新的分區(qū)然后把它加入到新生代分區(qū)列表中。G1有一個線程專門抽樣處理預測新生代列表的長度應該多大,并動態(tài)調整。
另外還有一個問題,就是分配新的分區(qū)時,何時擴展?一次擴展多少內存?
G1是自適應擴展內存空間的。參數(shù)-XX:GCTimeRatio表示GC與應用的耗費時間比,G1中默認為9,計算方式為_gc_overhead_perc=100.0×(1.0/(1.0+GCTimeRatio)),即G1 GC時間與應用時間占比不超過10%時不需要動態(tài)擴展,當GC時間超過這個閾值的10%,可以動態(tài)擴展。擴展時有一個參數(shù)G1ExpandByPercentOfAvailable(默認值是20)來控制一次擴展的比例,即每次都至少從未提交的內存中申請20%,有下限要求(一次申請的內存不能少于1M,最多是當前已分配的一倍),代碼如下所示:
size_t G1CollectorPolicy::expansion_amount() { // 先根據(jù)歷史信息獲取平均GC時間 double recent_gc_overhead = recent_avg_pause_time_ratio() * 100.0; double threshold = _gc_overhead_perc; /* G1 GC時間與應用時間占比超過閾值才需要動態(tài)擴展,這個閾值的值為_gc_overhead_perc = 100.0 × (1.0 / (1.0 + GCTimeRatio)),上文提到GCTimeRatio=9,即超過10%才會擴張內存*/ if (recent_gc_overhead > threshold) { const size_t min_expand_bytes = 1*M; size_t reserved_bytes = _g1->max_capacity(); size_t committed_bytes = _g1->capacity(); size_t uncommitted_bytes = reserved_bytes - committed_bytes; size_t expand_bytes; size_t expand_bytes_via_pct = uncommitted_bytes * G1ExpandByPercentOfAvailable / 100; expand_bytes = MIN2(expand_bytes_via_pct, committed_bytes); expand_bytes = MAX2(expand_bytes, min_expand_bytes); expand_bytes = MIN2(expand_bytes, uncommitted_bytes); …… return expand_bytes; } else { return 0; } }
GC中內存的擴展時機在第5章介紹。
- Boost C++ Application Development Cookbook(Second Edition)
- MATLAB圖像處理超級學習手冊
- CentOS 7 Server Deployment Cookbook
- Python完全自學教程
- R大數(shù)據(jù)分析實用指南
- Getting Started with NativeScript
- Unreal Engine 4 Shaders and Effects Cookbook
- Android開發(fā):從0到1 (清華開發(fā)者書庫)
- Julia高性能科學計算(第2版)
- Babylon.js Essentials
- Node.js開發(fā)指南
- Kotlin進階實戰(zhàn)
- Offer來了:Java面試核心知識點精講(框架篇)
- 開發(fā)者測試
- 中小企業(yè)網站建設與管理(靜態(tài)篇)