通過HotSpot源碼詳解Java堆空間創(chuàng)建過程

前言

我們每天都在與Java堆打交道岔留,對它的組成與調(diào)優(yōu)都有了比較深刻的理解庵佣。Java堆的簡單示意圖如下。

JDK 7-還是永久代(Perm)婿斥,JDK 8+就是元空間(Metaspace)了

不過劝篷,你有沒有想過堆空間到底是怎么產(chǎn)生的呢?要搞清楚這個問題民宿,就得通過閱讀JVM的源碼來追根究底了娇妓。本文分析的是OpenJDK 7u版本的源碼,OpenJDK源碼可以在其非官方的GitHub Repo上clone活鹰。建議用一個趁手的C/C++ IDE導入源碼哈恰,方便閱讀,我用的是CLion志群。

Java堆初始化的入口為Universe::initialize_heap()方法着绷,位于hotspot/src/share/vm/memory/universe.cpp文件中。下面開始咯锌云。

根據(jù)GC方式確定GC策略與堆實現(xiàn)

這部分的源碼如下荠医。

  if (UseParallelGC) {
#ifndef SERIALGC
    Universe::_collectedHeap = new ParallelScavengeHeap();
#else  // SERIALGC
    fatal("UseParallelGC not supported in java kernel vm.");
#endif // SERIALGC

  } else if (UseG1GC) {
#ifndef SERIALGC
    G1CollectorPolicy* g1p = new G1CollectorPolicy();
    G1CollectedHeap* g1h = new G1CollectedHeap(g1p);
    Universe::_collectedHeap = g1h;
#else  // SERIALGC
    fatal("UseG1GC not supported in java kernel vm.");
#endif // SERIALGC

  } else {
    GenCollectorPolicy *gc_policy;

    if (UseSerialGC) {
      gc_policy = new MarkSweepPolicy();
    } else if (UseConcMarkSweepGC) {
#ifndef SERIALGC
      if (UseAdaptiveSizePolicy) {
        gc_policy = new ASConcurrentMarkSweepPolicy();
      } else {
        gc_policy = new ConcurrentMarkSweepPolicy();
      }
#else   // SERIALGC
    fatal("UseConcMarkSweepGC not supported in java kernel vm.");
#endif // SERIALGC
    } else { // default old generation
      gc_policy = new MarkSweepPolicy();
    }

    Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
  }

  jint status = Universe::heap()->initialize();
  if (status != JNI_OK) {
    return status;
  }

該段代碼的執(zhí)行流程是:

  1. 如果JVM使用了并行收集器(-XX:+UseParallelGC),則將堆初始化為ParallelScavengeHeap類型桑涎,即并行收集堆彬向。
  2. 如果JVM使用了G1收集器(-XX:+UseG1GC),則將堆初始化為G1CollectedHeap類型攻冷,即G1堆娃胆。同時設置GC策略為G1專用的G1CollectorPolicy。
  3. 如果沒有選擇以上兩種收集器讲衫,就繼續(xù)檢查是否使用了串行收集器(-XX:+UseSerialGC)缕棵,如是,設置GC策略為MarkSweepPolicy涉兽,即標記-清除招驴。
  4. 再檢查到如果使用了CMS收集器(-XX:+UseConcMarkSweepGC),就根據(jù)是否啟用自適應開關(-XX:+UseAdaptiveSizePolicy)枷畏,設置GC策略為自適應的ASConcurrentMarkSweepPolicy别厘,或者標準的ConcurrentMarkSweepPolicy。
  5. 如果以上情況都沒有配置拥诡,就采用默認的GC策略為MarkSweepPolicy触趴。對于步驟3~5的所有情況,都會將堆初始化為GenCollectedHeap類型渴肉,即分代收集堆冗懦。
  6. 調(diào)用各堆實現(xiàn)類對應的initialize()方法執(zhí)行堆的初始化操作。

下圖示出分代GC策略類以及各堆實現(xiàn)類的繼承體系仇祭。

構造GC策略與堆參數(shù)

為了方便講解披蕉,我們以較常用的CMS標準GC策略ConcurrentMarkSweepPolicy為例來探討。先來看其構造方法乌奇。

ConcurrentMarkSweepPolicy::ConcurrentMarkSweepPolicy() {
  initialize_all();
}

該initialize_all()方法由ConcurrentMarkSweepPolicy的父類GenCollectorPolicy來定義没讲。

  virtual void initialize_all() {
    initialize_flags();
    initialize_size_info();
    initialize_generations();
  }

可見,這個方法直接調(diào)用了另外三個以initialize為前綴的方法礁苗,它們分別完成特定的功能爬凑,下面按順序來看。

initialize_flags()方法:對齊與校驗

GenCollectorPolicy::initialize_flags()方法的源碼如下试伙。

void GenCollectorPolicy::initialize_flags() {
  // All sizes must be multiples of the generation granularity.
  set_min_alignment((uintx) Generation::GenGrain);
  set_max_alignment(compute_max_alignment());

  CollectorPolicy::initialize_flags();

  // All generational heaps have a youngest gen; handle those flags here.

  // Adjust max size parameters
  if (NewSize > MaxNewSize) {
    MaxNewSize = NewSize;
  }
  NewSize = align_size_down(NewSize, min_alignment());
  MaxNewSize = align_size_down(MaxNewSize, min_alignment());

  // Check validity of heap flags
  assert(NewSize     % min_alignment() == 0, "eden space alignment");
  assert(MaxNewSize  % min_alignment() == 0, "survivor space alignment");

  if (NewSize < 3*min_alignment()) {
     // make sure there room for eden and two survivor spaces
    vm_exit_during_initialization("Too small new size specified");
  }
  if (SurvivorRatio < 1 || NewRatio < 1) {
    vm_exit_during_initialization("Invalid heap ratio specified");
  }
}

這個方法首先調(diào)用set_min_alignment()/set_max_alignment()設置堆空間的對齊嘁信,來看一下最小對齊的定義。

  enum SomePublicConstants {
    LogOfGenGrain = 16 ARM_ONLY(+1),
    GenGrain = 1 << LogOfGenGrain
  };

這里定義了分代堆空間的粒度迁霎,即216B = 64KB吱抚。也就是說各代必須至少按64KB對齊。

最大對齊則通過調(diào)用compute_max_alignment()方法來計算考廉,代碼如下秘豹。注釋很有用,所以我留下了昌粤。

size_t GenCollectorPolicy::compute_max_alignment() {
  // The card marking array and the offset arrays for old generations are
  // committed in os pages as well. Make sure they are entirely full (to
  // avoid partial page problems), e.g. if 512 bytes heap corresponds to 1
  // byte entry and the os page size is 4096, the maximum heap size should
  // be 512*4096 = 2MB aligned.
  size_t alignment = GenRemSet::max_alignment_constraint(rem_set_name());

  // Parallel GC does its own alignment of the generations to avoid requiring a
  // large page (256M on some platforms) for the permanent generation.  The
  // other collectors should also be updated to do their own alignment and then
  // this use of lcm() should be removed.
  if (UseLargePages && !UseParallelGC) {
      // in presence of large pages we have to make sure that our
      // alignment is large page aware
      alignment = lcm(os::large_page_size(), alignment);
  }

  assert(alignment >= min_alignment(), "Must be");

  return alignment;
}

這里可以多說兩句既绕。GenRemSet是JVM中維護跨代引用的數(shù)據(jù)結構,通用名稱為“記憶集合”(Remember Set)涮坐。對于常見的兩分代堆而言凄贩,跨代引用就是老生代中存在指向新生代對象的引用,如果不預先維護的話袱讹,每次新生代GC都要去掃描老生代疲扎,非常麻煩昵时。GenRemSet的經(jīng)典實現(xiàn)是卡表(CardTableRS),本質是字節(jié)數(shù)組椒丧,每個字節(jié)(即一張卡)對應老生代中一段連續(xù)的內(nèi)存是否有跨代引用壹甥,如圖所示。

那么卡表與最大對齊有什么關系呢壶熏?看以下方法句柠。

uintx CardTableModRefBS::ct_max_alignment_constraint() {
  return card_size * os::vm_page_size();
}

其中card_size為29 = 512,也就是每張卡對應512B的老生代內(nèi)存棒假。將它與JVM的普通頁大兴葜啊(一般是4KB)相乘,就是最大對齊帽哑。如果JVM啟用了大內(nèi)存分頁谜酒,就繼續(xù)用上面的計算結果與大頁大小(一般是2MB或4MB)取最小公倍數(shù)作為最大對齊妻枕。

堆空間對齊設置完了甚带,接下來調(diào)用父類CollectorPolicy的同名方法,校驗永久代大屑淹贰(-XX:PermSize鹰贵、-XX:MaxPermSize)以及一些其他配置。它的流程與本方法實現(xiàn)的校驗新生代大小比較像康嘉,為節(jié)省篇幅碉输,不再給出代碼。

我們已經(jīng)知道亭珍,新生代可以通過-XX:NewSize敷钾、-XX:MaxNewSize-Xmn三個參數(shù)來設定,設定-Xmn就相當于將前兩個參數(shù)設為相同的值肄梨。接下來就將NewSize與MaxNewSize按64KB向下對齊阻荒,并確定它們是64KB的倍數(shù)。向下與向上對齊方法的源碼如下众羡,基于宏定義侨赡,本質是位運算。

#define align_size_down_(size, alignment) ((size) & ~((alignment) - 1))

inline intptr_t align_size_down(intptr_t size, intptr_t alignment) {
  return align_size_down_(size, alignment);
}

#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1))

inline intptr_t align_size_up(intptr_t size, intptr_t alignment) {
  return align_size_up_(size, alignment);
}

因為新生代由一個Eden區(qū)與兩個Survivor區(qū)組成粱侣,所以NewSize不能小于3 * 64 = 192KB羊壹。另外,-XX:NewRatio-XX:SurvivorRatio都不能小于1齐婴,亦即老生代與新生代的比例不能小于1:1油猫,Eden區(qū)與Survivor區(qū)的比例不能小于1:2。

需要注意的是柠偶,GenCollectorPolicy的子類TwoGenerationCollectorPolicy中也有一個同名方法情妖。它先調(diào)用了父類的方法睬关,然后校驗老生代和最大堆大小,代碼比較簡單毡证,如下共螺。

void TwoGenerationCollectorPolicy::initialize_flags() {
  GenCollectorPolicy::initialize_flags();

  OldSize = align_size_down(OldSize, min_alignment());
  if (NewSize + OldSize > MaxHeapSize) {
    MaxHeapSize = NewSize + OldSize;
  }
  MaxHeapSize = align_size_up(MaxHeapSize, max_alignment());

  always_do_update_barrier = UseConcMarkSweepGC;

  // Check validity of heap flags
  assert(OldSize     % min_alignment() == 0, "old space alignment");
  assert(MaxHeapSize % max_alignment() == 0, "maximum heap alignment");
}

老生代大小OldSize對應JVM參數(shù)中的-XX:OldSize,最大堆大小MaxHeapSize自然對應-Xmx情竹。這樣夏伊,新生代挽牢、老生代和永久代的參數(shù)就都對齊并校驗完畢了点把。

initialize_size_info()方法:設置堆與分代大小

與上面的initialize_flags()方法相似爽撒,這個方法在CollectorPolicy娃承、GenCollectorPolicy碴巾、TwoGenerationCollectorPolicy中各有一個模捂,分別負責真正設置整個堆勋拟、新生代和老生代的大小法梯,并且同樣是鏈式調(diào)用苔货。它們的代碼都很長,但功能單一立哑,都是比較夜惭、對齊與賦值操作。選擇設置新生代的GenCollectorPolicy::initialize_size_info()來看看铛绰。

void GenCollectorPolicy::initialize_size_info() {
  CollectorPolicy::initialize_size_info();

  size_t max_new_size = 0;
  if (FLAG_IS_CMDLINE(MaxNewSize) || FLAG_IS_ERGO(MaxNewSize)) {
    if (MaxNewSize < min_alignment()) {
      max_new_size = min_alignment();
    }
    if (MaxNewSize >= max_heap_byte_size()) {
      max_new_size = align_size_down(max_heap_byte_size() - min_alignment(),
                                     min_alignment());
      warning("MaxNewSize (" SIZE_FORMAT "k) is equal to or "
        "greater than the entire heap (" SIZE_FORMAT "k).  A "
        "new generation size of " SIZE_FORMAT "k will be used.",
        MaxNewSize/K, max_heap_byte_size()/K, max_new_size/K);
    } else {
      max_new_size = align_size_down(MaxNewSize, min_alignment());
    }

  } else {
    max_new_size = scale_by_NewRatio_aligned(max_heap_byte_size());
    max_new_size = MIN2(MAX2(max_new_size, NewSize), MaxNewSize);
  }
  assert(max_new_size > 0, "All paths should set max_new_size");

  if (max_heap_byte_size() == min_heap_byte_size()) {
    set_min_gen0_size(max_new_size);
    set_initial_gen0_size(max_new_size);
    set_max_gen0_size(max_new_size);
  } else {
    size_t desired_new_size = 0;
    if (!FLAG_IS_DEFAULT(NewSize)) {
      _min_gen0_size = NewSize;
      desired_new_size = NewSize;
      max_new_size = MAX2(max_new_size, NewSize);
    } else {
      _min_gen0_size = MAX2(scale_by_NewRatio_aligned(min_heap_byte_size()),
                          NewSize);
      desired_new_size =
        MAX2(scale_by_NewRatio_aligned(initial_heap_byte_size()),
             NewSize);
    }

    assert(_min_gen0_size > 0, "Sanity check");
    set_initial_gen0_size(desired_new_size);
    set_max_gen0_size(max_new_size);
    // ...后面一大堆set方法調(diào)用诈茧,略去...
  }

  if (PrintGCDetails && Verbose) {
    gclog_or_tty->print_cr(/*...*/);
  }
}

解釋起來肯定很費勁,并且也沒有必要逐句讀捂掰,只說三個值得注意的點敢会。

  • 新生代的通用名稱其實是gen0。相對地这嚣,老生代就是gen1鸥昏。
  • 設定大小時,是先計算MaxNewSize姐帚,再計算NewSize吏垮。設定之前如果經(jīng)過了運算,就必須重新對齊罐旗。
  • 如果用-XX:MaxNewSize-Xmn顯式指定了新生代最大大小惫皱,或者JVM自動優(yōu)化(Ergonomics)機制更改了該值,-XX:NewRatio的值就會被忽略尤莺。NewSize同理旅敷。

另外兩個initialize_size_info()在代碼庫中能找到,看官有興趣的話可以自己去看颤霎。

initialize_generations()方法:生成分代管理器

雖然該方法的名字是“初始化分代”的意思媳谁,但它還不會執(zhí)行真正的初始化動作涂滴,而是生成GenerationSpec實例,該實例內(nèi)含有分代的描述信息(名稱晴音、大小等)柔纵,在真正初始化分代時需要用到。這個方法由ConcurrentMarkSweepPolicy自己實現(xiàn)锤躁,代碼如下搁料。

void ConcurrentMarkSweepPolicy::initialize_generations() {
  initialize_perm_generation(PermGen::ConcurrentMarkSweep);
  _generations = new GenerationSpecPtr[number_of_generations()];
  if (_generations == NULL)
    vm_exit_during_initialization("Unable to allocate gen spec");

  if (ParNewGeneration::in_use()) {
    if (UseAdaptiveSizePolicy) {
      _generations[0] = new GenerationSpec(Generation::ASParNew,
                                           _initial_gen0_size, _max_gen0_size);
    } else {
      _generations[0] = new GenerationSpec(Generation::ParNew,
                                           _initial_gen0_size, _max_gen0_size);
    }
  } else {
    _generations[0] = new GenerationSpec(Generation::DefNew,
                                         _initial_gen0_size, _max_gen0_size);
  }
  if (UseAdaptiveSizePolicy) {
    _generations[1] = new GenerationSpec(Generation::ASConcurrentMarkSweep,
                            _initial_gen1_size, _max_gen1_size);
  } else {
    _generations[1] = new GenerationSpec(Generation::ConcurrentMarkSweep,
                            _initial_gen1_size, _max_gen1_size);
  }

  if (_generations[0] == NULL || _generations[1] == NULL) {
    vm_exit_during_initialization("Unable to allocate gen spec");
  }
}

可見,首先調(diào)用initialize_perm_generation()方法生成永久代對應的PermanentGenerationSpec(代碼略)系羞。然后郭计,檢查是否符合ParNewGeneration::in_use()的條件,即啟用并行新生代GC(-XX:+UseParNewGC)并且GC線程數(shù)(-XX:ParallelGCThreads)>0椒振,如是昭伸,將新生代GenerationSpec的類型設置為ParNew,否則設為DefNew澎迎。老生代GenerationSpec的類型則固定為ConcurrentMarkSweep庐杨。

至此,初始化GC策略與堆參數(shù)的工作就完成了夹供,下面主要是分配堆內(nèi)存空間與分代的過程灵份,還有一些其他的工作。

分配堆內(nèi)存空間與分代

前面已經(jīng)說過哮洽,各堆實現(xiàn)都有一個initialize()方法用于初始化各吨。GenCollectedHeap::initialize()方法很長,所以我們拆開來看袁铐。

最后一次對齊

在創(chuàng)建分代之前揭蜒,再將它們對齊一次,代碼如下剔桨。

  int i;
  _n_gens = gen_policy()->number_of_generations();

  guarantee(HeapWordSize == wordSize, "HeapWordSize must equal wordSize");

  size_t gen_alignment = Generation::GenGrain;

  _gen_specs = gen_policy()->generations();
  PermanentGenerationSpec *perm_gen_spec =
                                collector_policy()->permanent_generation();

  size_t heap_alignment = collector_policy()->max_alignment();

  for (i = 0; i < _n_gens; i++) {
    _gen_specs[i]->align(gen_alignment);
  }
  perm_gen_spec->align(heap_alignment);

number_of_generations()就是分代數(shù)量屉更,這里固定為2。新生代和老生代都是按最小粒度(即64KB)對齊洒缀,永久代則是按最大粒度對齊瑰谜。

分配堆內(nèi)存空間

代碼如下。

  char* heap_address;
  size_t total_reserved = 0;
  int n_covered_regions = 0;
  ReservedSpace heap_rs;

  heap_address = allocate(heap_alignment, perm_gen_spec, &total_reserved,
                          &n_covered_regions, &heap_rs);

  if (UseSharedSpaces) {
    if (!heap_rs.is_reserved() || heap_address != heap_rs.base()) {
      if (heap_rs.is_reserved()) {
        heap_rs.release();
      }
      FileMapInfo* mapinfo = FileMapInfo::current_info();
      mapinfo->fail_continue("Unable to reserve shared region.");
      allocate(heap_alignment, perm_gen_spec, &total_reserved, &n_covered_regions,
               &heap_rs);
    }
  }

  if (!heap_rs.is_reserved()) {
    vm_shutdown_during_initialization(
      "Could not reserve enough space for object heap");
    return JNI_ENOMEM;
  }

這段代碼中起主要作用的是allocate()方法树绩,其本質是將一段連續(xù)的內(nèi)存空間分配成ReservedSpace萨脑,即預留空間。下面重點看看allocate()方法的實現(xiàn)饺饭,其全貌如下渤早。

char* GenCollectedHeap::allocate(size_t alignment,
                                 PermanentGenerationSpec* perm_gen_spec,
                                 size_t* _total_reserved,
                                 int* _n_covered_regions,
                                 ReservedSpace* heap_rs){
  // Now figure out the total size.
  size_t total_reserved = 0;
  int n_covered_regions = 0;
  const size_t pageSize = UseLargePages ?
      os::large_page_size() : os::vm_page_size();

  assert(alignment % pageSize == 0, "Must be");

  for (int i = 0; i < _n_gens; i++) {
    total_reserved = add_and_check_overflow(total_reserved, _gen_specs[i]->max_size());
    n_covered_regions += _gen_specs[i]->n_covered_regions();
  }

  assert(total_reserved % alignment == 0,
         err_msg("Gen size; total_reserved=" SIZE_FORMAT ", alignment="
                 SIZE_FORMAT, total_reserved, alignment));
  total_reserved = add_and_check_overflow(total_reserved, perm_gen_spec->max_size());
  assert(total_reserved % alignment == 0,
         err_msg("Perm size; total_reserved=" SIZE_FORMAT ", alignment="
                 SIZE_FORMAT ", perm gen max=" SIZE_FORMAT, total_reserved,
                 alignment, perm_gen_spec->max_size()));

  n_covered_regions += perm_gen_spec->n_covered_regions();

  // Add the size of the data area which shares the same reserved area
  // as the heap, but which is not actually part of the heap.
  size_t misc = perm_gen_spec->misc_data_size() + perm_gen_spec->misc_code_size();
  total_reserved = add_and_check_overflow(total_reserved, misc);

  if (UseLargePages) {
    assert(misc == 0, "CDS does not support Large Pages");
    assert(total_reserved != 0, "total_reserved cannot be 0");
    assert(is_size_aligned(total_reserved, os::large_page_size()), "Must be");
    total_reserved = round_up_and_check_overflow(total_reserved, os::large_page_size());
  }

  // Calculate the address at which the heap must reside in order for
  // the shared data to be at the required address.

  char* heap_address;
  if (UseSharedSpaces) {

    // Calculate the address of the first word beyond the heap.
    FileMapInfo* mapinfo = FileMapInfo::current_info();
    int lr = CompactingPermGenGen::n_regions - 1;
    size_t capacity = align_size_up(mapinfo->space_capacity(lr), alignment);
    heap_address = mapinfo->region_base(lr) + capacity;

    // Calculate the address of the first word of the heap.
    heap_address -= total_reserved;
  } else {
    heap_address = NULL;  // any address will do.
    if (UseCompressedOops) {
      heap_address = Universe::preferred_heap_base(total_reserved, alignment, Universe::UnscaledNarrowOop);
      *_total_reserved = total_reserved;
      *_n_covered_regions = n_covered_regions;
      *heap_rs = ReservedHeapSpace(total_reserved, alignment,
                                   UseLargePages, heap_address);

      if (heap_address != NULL && !heap_rs->is_reserved()) {
        // Failed to reserve at specified address - the requested memory
        // region is taken already, for example, by 'java' launcher.
        // Try again to reserver heap higher.
        heap_address = Universe::preferred_heap_base(total_reserved, alignment, Universe::ZeroBasedNarrowOop);
        *heap_rs = ReservedHeapSpace(total_reserved, alignment,
                                     UseLargePages, heap_address);

        if (heap_address != NULL && !heap_rs->is_reserved()) {
          // Failed to reserve at specified address again - give up.
          heap_address = Universe::preferred_heap_base(total_reserved, alignment, Universe::HeapBasedNarrowOop);
          assert(heap_address == NULL, "");
          *heap_rs = ReservedHeapSpace(total_reserved, alignment,
                                       UseLargePages, heap_address);
        }
      }
      return heap_address;
    }
  }

  *_total_reserved = total_reserved;
  *_n_covered_regions = n_covered_regions;
  *heap_rs = ReservedHeapSpace(total_reserved, alignment,
                               UseLargePages, heap_address);

  return heap_address;
}

該方法的大致執(zhí)行流程如下,有點長:

  1. 確定當前的頁大小瘫俊。
  2. 根據(jù)新生代鹊杖、老生代和永久代的各個GenerationSpec悴灵,將它們的最大內(nèi)存大小累加到total_reserved變量,作為申請內(nèi)存的總量骂蓖。
  3. 同時將GenerationSpec中的n_covered_regions一同累加积瞒,該字段代表申請內(nèi)存區(qū)域的數(shù)量,新生代登下、老生代都為1茫孔,永久代為2。
  4. 如果配置為大頁模式被芳,將申請內(nèi)存的量向上對齊到頁大小缰贝。
  5. 若啟用了壓縮普通對象指針(-XX:+UseCompressedOops),調(diào)用Universe::preferred_heap_base()方法筐钟,以32位直接壓縮的方式(UnscaledNarrowOop)取得堆的基地址,并調(diào)用ReservedHeapSpace的構造方法赋朦,申請內(nèi)存篓冲。
  6. 如果上一步申請失敗,說明比4GB大宠哄,就以零基地址壓縮的方式(ZeroBasedNarrowOop)在更高的地址空間上取得堆的基地址并申請內(nèi)存壹将。
  7. 如果仍然申請失敗,說明比32GB還大毛嫉,就只能用普通的指針壓縮方式(HeapBasedNarrowOop)取得堆的基地址并申請內(nèi)存诽俯。
  8. 如果沒有啟用壓縮普通對象指針,就直接用ReservedHeapSpace申請內(nèi)存承粤。最終都返回起始地址暴区。

那么真正分配內(nèi)存的邏輯在哪里呢?它位于ReservedHeapSpace的父類——ReservedSpace的initialize()方法中辛臊,為了節(jié)省篇幅仙粱,只看最核心的一段。

void ReservedSpace::initialize(size_t size, size_t alignment, bool large,
                               char* requested_address,
                               const size_t noaccess_prefix,
                               bool executable) {
  // ...
  if (base == NULL) {
    // Optimistically assume that the OSes returns an aligned base pointer.
    // When reserving a large address range, most OSes seem to align to at
    // least 64K.

    // If the memory was requested at a particular address, use
    // os::attempt_reserve_memory_at() to avoid over mapping something
    // important.  If available space is not detected, return NULL.

    if (requested_address != 0) {
      base = os::attempt_reserve_memory_at(size, requested_address);
      if (failed_to_reserve_as_requested(base, requested_address, size, false)) {
        // OS ignored requested address. Try different address.
        base = NULL;
      }
    } else {
      base = os::reserve_memory(size, NULL, alignment);
    }

    if (base == NULL) return;

    // Check alignment constraints
    if ((((size_t)base + noaccess_prefix) & (alignment - 1)) != 0) {
      // Base not aligned, retry
      release_memory(base, size);

      // Make sure that size is aligned
      size = align_size_up(size, alignment);
      base = os::reserve_memory_aligned(size, alignment);

      if (requested_address != 0 &&
          failed_to_reserve_as_requested(base, requested_address, size, false)) {
        // As a result of the alignment constraints, the allocated base differs
        // from the requested address. Return back to the caller who can
        // take remedial action (like try again without a requested address).
        assert(_base == NULL, "should be");
        return;
      }
    }
  }
  // Done ...

可見彻舰,如果堆要在指定地址分配伐割,亦即配置了共享空間或者指針壓縮,就調(diào)用os::attempt_reserve_memory_at()申請內(nèi)存刃唤,否則就調(diào)用os::reserve_memory()方法申請內(nèi)存隔心。申請成功之后仍然要對齊,方法是先檢查基地址是否對齊尚胞,如果沒有硬霍,就直接釋放掉分配的空間,將內(nèi)存大小向上對齊之后笼裳,調(diào)用os::reserve_memory_aligned()重新申請一塊對齊的空間须尚。

調(diào)整堆大小并創(chuàng)建GenRemSet

  _reserved = MemRegion((HeapWord*)heap_rs.base(),
                        (HeapWord*)(heap_rs.base() + heap_rs.size()));
  _reserved.set_word_size(0);
  _reserved.set_start((HeapWord*)heap_rs.base());
  size_t actual_heap_size = heap_rs.size() - perm_gen_spec->misc_data_size()
                                           - perm_gen_spec->misc_code_size();
  _reserved.set_end((HeapWord*)(heap_rs.base() + actual_heap_size));

  _rem_set = collector_policy()->create_rem_set(_reserved, n_covered_regions);
  set_barrier_set(rem_set()->bs());

可見崖堤,這段代碼首先將堆空間封裝成一個MemRegion對象,然后將前面的堆大小減去永久代中Misc Code與Misc Data兩個區(qū)域的大小耐床,就是堆的實際大小密幔。最后,調(diào)用GC策略的create_rem_set()方法生成GenRemSet的實現(xiàn)撩轰,對于CMS而言就是CardTableRS胯甩,即卡表。

分代初始化
  for (i = 0; i < _n_gens; i++) {
    ReservedSpace this_rs = heap_rs.first_part(_gen_specs[i]->max_size(),
                                              UseSharedSpaces, UseSharedSpaces);
    _gens[i] = _gen_specs[i]->init(this_rs, i, rem_set());
    // tag generations in JavaHeap
    MemTracker::record_virtual_memory_type((address)this_rs.base(), mtJavaHeap);
    heap_rs = heap_rs.last_part(_gen_specs[i]->max_size());
  }
  _perm_gen = perm_gen_spec->init(heap_rs, PermSize, rem_set());

各個GenerationSpec中都有一個init()方法來初始化它對應的分代堪嫂,主體是一個switch-case結構偎箫。我們只來關注一下CMS情況下的ParNew與ConcurrentMarkSweep分代實現(xiàn)。

Generation* GenerationSpec::init(ReservedSpace rs, int level,
                                 GenRemSet* remset) {
  switch (name()) {
    case Generation::DefNew: // ...
    case Generation::MarkSweepCompact: // ...

#ifndef SERIALGC
    case Generation::ParNew:
      return new ParNewGeneration(rs, init_size(), level);

    case Generation::ASParNew: // ...

    case Generation::ConcurrentMarkSweep: {
      assert(UseConcMarkSweepGC, "UseConcMarkSweepGC should be set");
      CardTableRS* ctrs = remset->as_CardTableRS();
      if (ctrs == NULL) {
        vm_exit_during_initialization("Rem set incompatibility.");
      }
      // Otherwise
      // The constructor creates the CMSCollector if needed,
      // else registers with an existing CMSCollector

      ConcurrentMarkSweepGeneration* g = NULL;
      g = new ConcurrentMarkSweepGeneration(rs,
                 init_size(), level, ctrs, UseCMSAdaptiveFreeLists,
                 (FreeBlockDictionary<FreeChunk>::DictionaryChoice)CMSDictionaryChoice);

      g->initialize_performance_counters();

      return g;
    }

    case Generation::ASConcurrentMarkSweep:  // ...
#endif // SERIALGC

    default:
      guarantee(false, "unrecognized GenerationName");
      return NULL;
  }
}

可見皆串,新生代對應的是ParNewGeneration實現(xiàn)淹办,老生代對應的是ConcurrentMarkSweepGeneration實現(xiàn)。根據(jù)GenerationSpec中記錄的內(nèi)存大小恶复,就可以將之前申請的堆空間劃分給各個代怜森。整個堆空間至此就基本創(chuàng)建完成了。

總結

這篇寫了3天谤牡,不想寫總結了副硅,晚安吧各位_(′?`」 ∠)_

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翅萤,隨后出現(xiàn)的幾起案子恐疲,更是在濱河造成了極大的恐慌,老刑警劉巖套么,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件培己,死亡現(xiàn)場離奇詭異,居然都是意外死亡胚泌,警方通過查閱死者的電腦和手機漱凝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诸迟,“玉大人茸炒,你說我怎么就攤上這事≌笪” “怎么了壁公?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绅项。 經(jīng)常有香客問我紊册,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任囊陡,我火速辦了婚禮芳绩,結果婚禮上,老公的妹妹穿的比我還像新娘撞反。我一直安慰自己妥色,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布遏片。 她就那樣靜靜地躺著嘹害,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吮便。 梳的紋絲不亂的頭發(fā)上笔呀,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音髓需,去河邊找鬼许师。 笑死,一個胖子當著我的面吹牛僚匆,可吹牛的內(nèi)容都是我干的微渠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼白热,長吁一口氣:“原來是場噩夢啊……” “哼敛助!你這毒婦竟也來了粗卜?” 一聲冷哼從身側響起屋确,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎续扔,沒想到半個月后攻臀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡纱昧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年刨啸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片识脆。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡设联,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灼捂,到底是詐尸還是另有隱情离例,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布悉稠,位于F島的核電站宫蛆,受9級特大地震影響,放射性物質發(fā)生泄漏的猛。R本人自食惡果不足惜耀盗,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一想虎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叛拷,春花似錦舌厨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煌恢,卻和暖如春骇陈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瑰抵。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工你雌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人二汛。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓婿崭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肴颊。 傳聞我的和親對象是個殘疾皇子氓栈,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容