官术网_书友最值得收藏!

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章介紹。

主站蜘蛛池模板: 长寿区| 山东| 敦化市| 札达县| 西安市| 苗栗市| 兴国县| 津南区| 乌拉特前旗| 巴里| 广安市| 庄河市| 贺州市| 梓潼县| 尉犁县| 繁昌县| 大方县| 出国| 苍梧县| 德阳市| 桐梓县| 成都市| 峨眉山市| 雅安市| 雅安市| 阳信县| 明水县| 东光县| 临夏市| 蒲江县| 广宁县| 兴业县| 罗源县| 腾冲县| 临澧县| 喀喇沁旗| 青阳县| 东乡族自治县| 谢通门县| 临沂市| 古交市|