国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

源碼分析:Java堆的創建

2019-11-14 15:08:10
字體:
來源:轉載
供稿:網友

  虛擬機在內存中申請一片區域,由虛擬機自動管理,用來滿足應用程序對象分配的空間需求,即堆空間。

  由于程序運行的局部特性,程序創建的大多數對象都具有非常短的生命周期,而程序也會創建一些生命周期特別長的對象。簡單的復制收集器無論對象的生命周期是長是短,都會進行復制操作。而生命周期較長的對象在多次垃圾回收期間內并不會被回收,這就使得這些對象被來回復制而使得算法性能大大下降。

  分代收集把堆分為多個子堆,分別用來存放不同壽命的對象。新生對象空間的將經歷最頻繁的垃圾回收,而對于經歷了若干次垃圾收集后仍然存活的對象,將成長為成熟對象,并移動到成熟對象的子堆中,而對老生代子堆的垃圾回收就不會像新生對象子堆那么頻繁。

  HotSpot的堆空間分為新生代(YoungGen)和老年代(OldGen,此外還有位于非堆空間的永久代,但在java8中將移除永久代),新生代又分為Eden區和2個Survivor區(From/To)用以進行復制收集垃圾對象。 
對Java堆和對象的分析將從Java堆的創建開始,然后分析Java對象的分配與垃圾回收。

 一、堆的實現方式

    在虛擬機的創建初始化過程中,通過調用Universe的成員函數initialize_heap()將完成Java堆的初始化。在Universe模塊下的初始化將根據虛擬機選項來選擇堆的具體實現方式: 
  1.若虛擬機配置UseParallelGC,則Java堆的堆類型為ParallelScavengeHeap(并行收集堆)

//定義在/hotspot/src/share/vm/memory/universe.cpp中if (UseParallelGC) {#ifndef SERIALGC    Universe::_collectedHeap = new ParallelScavengeHeap();#else  // SERIALGC    fatal("UseParallelGC not supported in java kernel vm.");#endif // SERIALGC  }

  2.若虛擬機配置UseG1GC,那么將選擇堆類型為G1CollectedHeap,垃圾收集策略將使用專用的G1CollectorPolicy(垃圾優先收集)策略

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

  3.否則,虛擬機將使用GenCollectedHeap(分代收集堆)

Universe::_collectedHeap = new GenCollectedHeap(gc_policy);

  各個堆實現類的類關系如下:

  對于默認情況下的堆實現,還要根據配置選擇垃圾回收策略gc_policy來構造一個GenCollectedHeap,這里根據虛擬機配置選擇不同的GC策略: 
  (1).若虛擬機配置UseSerialGC,那么將使用MarkSweepPolicy(標記-清除)策略

GenCollectorPolicy *gc_policy;    if (UseSerialGC) {      gc_policy = new MarkSweepPolicy();    }

  (2).若虛擬機配置UseConcMarkSweepGC和UseAdaptiveSizePolicy,那么將使用ASConcurrentMarkSweepPolicy(自適應并發標記-清除)策略,若沒有指定UseAdaptiveSizePolicy,虛擬機將默認使用ConcurrentMarkSweepPolicy(并發標記-清除)策略

else if (UseConcMarkSweepGC) {#ifndef SERIALGC      if (UseAdaptiveSizePolicy) {        gc_policy = new ASConcurrentMarkSweepPolicy();      } else {        gc_policy = new ConcurrentMarkSweepPolicy();      }

  (3).若沒有進行配置,虛擬機將默認使用MarkSweepPolicy策略

else { // default old generation      gc_policy = new MarkSweepPolicy();    }

如下表所示:

 

其中垃圾回收策略類的關系如下圖:

  4.接下來是相應實現的堆的初始化

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

  5.堆空間初始化完成后,是LP64平臺上的指針壓縮以及TLAB的相關內容 。
  通常64位JVM消耗的內存會比32位的大1.5倍,這是因為在64位環境下,對象將使用64位指針,這就增加了一倍的指針占用內存開銷。從JDK 1.6 update14開始,64 bit JVM正式支持了 -XX:+UseComPRessedOops 選項來壓縮指針,以節省內存空間。 
指針壓縮的地址計算如下:

addr = <narrow_oop_base> + <narrow_oop> << 3 + <field_offset>

  若堆尋址空間小于4GB(2^32)時,直接使用32位的壓縮對象指針< narrow_oop >就可以找到該對象 
  若堆尋址空間大于4GB(2^32)但小于32GB時,就必須借助偏移來獲得真正的地址(對象是8字節對齊的)。 
  若堆尋址空間大于32GB時,就需要借助堆的基址來完成尋址了,< narrow_oop_base >為堆的基址,< field_offset >為一頁的大小。 
  (1).若heap的地址空間的最大地址大于OopEncodingHeapMax(32GB),則設置基礎地址為當前堆的起始地址-頁大小,設置偏移為LogMinObjAlignmentInBytes(3),即使用普通的對象指針壓縮技術

if ((uint64_t)Universe::heap()->reserved_region().end() > OopEncodingHeapMax) {      // Can't reserve heap below 32Gb.      Universe::set_narrow_oop_base(Universe::heap()->base() - os::vm_page_size());      Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);}

  (2).否則設置基礎地址為0

else {      Universe::set_narrow_oop_base(0);      //...      }

  若heap的地址空間的最大地址大于NarrowOopHeapMax(4GB,小于32GB),則設置偏移為LogMinObjAlignmentInBytes(默認為3),即使用零基壓縮技術,否則設置偏移為0,即直接使用壓縮對象指針進行尋址

if((uint64_t)Universe::heap()->reserved_region().end() > NarrowOopHeapMax) {        // Can't reserve heap below 4Gb.        Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);      } else {        Universe::set_narrow_oop_shift(0);

 二、堆的初始化:分代實現方式

 接下來分析特定堆的初始化過程,這里以GenCollectedHeap和MarkSweepPolicy為例:   

  GenCollectedHeap的構造函數中使用傳入的策略作為_gen_policy(代策略)。以MarkSweepPolicy為例,看看其構造函數:

//定義在/hotspot/src/share/vm/memory/collectorPolicy.cpp中MarkSweepPolicy::MarkSweepPolicy() {  initialize_all();}

  MarkSweepPolicy的構造函數調用了initialize_all()來完成策略的初始化,initialize_all()是父類GenCollectorPolicy()的虛函數,它調用了三個子初始化虛函數,這三個子初始化過程由GenCollectorPolicy的子類實現。其中initialize_flags()初始化了永久代的一些大小配置參數,initialize_size_info()設置了Java堆大小的相關參數,initialize_generations()根據用戶參數,配置各內存代的管理器。

//hotspot/src/share/vm/memory/collectorPolicy.hpp中virtual void initialize_all() {    initialize_flags();    initialize_size_info();    initialize_generations();  }

  下面通過initialize_generations()來看看各代有哪些實現方式: 
  1.若配置了UseParNewGC,并且并行GC線程數大于1,那么新生代就會使用ParNew實現

//永久代初始化  _generations = new GenerationSpecPtr[number_of_generations()];  //...  if (UseParNewGC && ParallelGCThreads > 0) {    _generations[0] = new GenerationSpec(Generation::ParNew, _initial_gen0_size, _max_gen0_size);  }

  2.默認新生代使用DefNew實現

else {    _generations[0] = new GenerationSpec(Generation::DefNew, _initial_gen0_size, _max_gen0_size);  }

  3.老年代固定使用MarkSweepCompact實現

_generations[1] = new GenerationSpec(Generation::MarkSweepCompact, _initial_gen1_size, _max_gen1_size);

(其中DefNew、ParNew、MarkSweepCompact等均為Generation的枚舉集合Name的成員,描述了可能實現的各種代實現類型) 
MarkSweepPolicy、ConcurrentMarkSweepPolicy、ASConcurrentMarkSweepPolicy對各代的實現綜合如下表所示: 

 

 三、堆的初始化:堆內存空間分配

  分析完了構造函數,回到Universe模塊中堆的initialize()。 
  以GenCollectedHeap為例: 
  1.根據構造函數傳入的gc_policy(分代策略)來初始化分代數

//定義在/hotspot/src/share/vm/memory/genCollectedHeap.cpp中jint GenCollectedHeap::initialize() {  //...  _n_gens = gen_policy()->number_of_generations();

  根據GenCollectedHeap的定義可以看到,GenCollectedHeap最多支持10個分代

 enum SomeConstants {    max_gens = 10  };//... private:  int _n_gens;  Generation* _gens[max_gens];

  其實并不需要這么多分代,MarkSweepPolicy、ConcurrentMarkSweepPolicy、ASConcurrentMarkSweepPolicy(ConcurrentMarkSweepPolicy的子類)均有著共同的祖先類TwoGenerationCollectorPolicy,其分代只有2代,即新生代和老年代。 

  2.每代的大小是基于GenGrain大小對齊的

 // The heap must be at least as aligned as generations.  size_t alignment = Generation::GenGrain;

GenGrain定義在/hotspot/src/share/vm/memory/generation.h中,在非ARM平臺中是2^16字節,即64KB大小 
  3.獲取各分代的管理器指針數組和永久代的管理器指針,并對齊各代的大小到64KB

PermanentGenerationSpec *perm_gen_spec =                                collector_policy()->permanent_generation();  // Make sure the sizes are all aligned.  for (i = 0; i < _n_gens; i++) {    _gen_specs[i]->align(alignment);  }  perm_gen_spec->align(alignment);

  GenerationSpec的align()定義在/hotspot/src/share/vm/memory/generationSpec.h,使初始和最大大小值向上對齊至64KB的倍數

// Alignment  void align(size_t alignment) {    set_init_size(align_size_up(init_size(), alignment));    set_max_size(align_size_up(max_size(), alignment));  }

  4.調用allocate()為堆分配空間,其起始地址為heap_address

char* heap_address;  size_t total_reserved = 0;  int n_covered_regions = 0;  ReservedSpace heap_rs(0);  heap_address = allocate(alignment, perm_gen_spec, &total_reserved,                          &n_covered_regions, &heap_rs);

  5.初始分配所得的空間將被封裝在_reserved(CollectedHeap的MemRegion成員)中

_reserved = MemRegion((HeapWord*)heap_rs.base(),                        (HeapWord*)(heap_rs.base() + heap_rs.size()));

  調整實際的堆大小為去掉永久代的misc_data和misc_code的空間,并創建一個覆蓋整個空間的數組,數組每個字節對應于堆的512字節,用于遍歷新生代和老年代空間

  _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());

  7.調用heap_rs的的first_part(),依次為新生代和老年代分配空間并調用各代管理器的init()將其初始化為Generation空間,最后為永久代分配空間和進行初始化

_gch = this;  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());    heap_rs = heap_rs.last_part(_gen_specs[i]->max_size());  }  _perm_gen = perm_gen_spec->init(heap_rs, PermSize, rem_set());

 

  四、內存空間申請實現

  那么GenCollectedHeap是如何向系統申請內存空間的呢? 
  答案就在allocate()函數中 
  1.在申請之前,當然要對內存空間的大小和分塊數進行計算 
  (1).內存頁的大小將根據虛擬機是否配置UseLargePages而不同,large_page_size在不同平臺上表現不同,x86使用2/4M(物理地址擴展模式)的頁大小,AMD64使用2M,否則,linux默認內存頁大小只有4KB,接下來會以各代所配置的最大大小進行計算,若最大值設置為負數,那么jvm將報錯退出,默認的新生代和老年代的分塊數為1,而永久代的分塊數為2

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();  for (int i = 0; i < _n_gens; i++) {    total_reserved += _gen_specs[i]->max_size();    if (total_reserved < _gen_specs[i]->max_size()) {      vm_exit_during_initialization(overflow_msg);    }    n_covered_regions += _gen_specs[i]->n_covered_regions();  }

  加上永久代空間的大小和塊數

total_reserved += perm_gen_spec->max_size();if (total_reserved < perm_gen_spec->max_size()) {    vm_exit_during_initialization(overflow_msg);  }  n_covered_regions += perm_gen_spec->n_covered_regions();

  (2).加上永久代的misc_data和misc_code的空間大小(數據區和代碼區),但其實并不是堆的一部分

size_t s = perm_gen_spec->misc_data_size() + perm_gen_spec->misc_code_size();  total_reserved += s;

  (3).如果配置了UseLargePages,那么將向上將申請的內存空間大小對齊至頁

if (UseLargePages) {    assert(total_reserved != 0, "total_reserved cannot be 0");    total_reserved = round_to(total_reserved, os::large_page_size());    if (total_reserved < os::large_page_size()) {      vm_exit_during_initialization(overflow_msg);    }  }

  (4).對象地址壓縮的內容 
根據UnscaledNarrowOop(直接使用壓縮指針)選取合適的堆起始地址,并嘗試在該地址上分配內存

 if (UseCompressedOops) {      heap_address = Universe::preferred_heap_base(total_reserved, Universe::UnscaledNarrowOop);      *_total_reserved = total_reserved;      *_n_covered_regions = n_covered_regions;      *heap_rs = ReservedHeapSpace(total_reserved, alignment,                                   UseLargePages, heap_address);

  若不能再該地址進行分配內存,則嘗試使用ZereBasedNarrowOop(零基壓縮)嘗試在更高的地址空間上進行分配

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, 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, Universe::HeapBasedNarrowOop);          assert(heap_address == NULL, "");          *heap_rs = ReservedHeapSpace(total_reserved, alignment,                                       UseLargePages, heap_address);        }      }

  2.調用ReservedHeapSpace的構造函數進行內存空間的申請

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

  在構造函數中并沒有發現對內存空間進行申請,那么繼續看父類ReservedSpace的構造函數

ReservedSpace::ReservedSpace(size_t size, size_t alignment,                               bool large,                               char* requested_address,                               const size_t noaccess_prefix) {    initialize(size+noaccess_prefix, alignment, large, requested_address,  noaccess_prefix, false);  } 

  3.initialize()的實現如下: 
  (1).如果目標操作系統不支持large_page_memory,那么將進行特殊處理,此外,對指針壓縮處理還需要對請求分配的內存空間大小進行調整

if (requested_address != 0) {      requested_address -= noaccess_prefix; // adjust requested address      assert(requested_address != NULL, "huge noaccess prefix?");    } 

  (2).對于上述特殊情況,會調用reserve_memory_special()進行內存空間的申請,并若申請成功會進行空間大小的對齊驗證

if (special) {      //向操作系統申請指定大小的內存,并映射到用戶指定的內存空間中      base = os::reserve_memory_special(size, requested_address, executable);      if (base != NULL) {        if (failed_to_reserve_as_requested(base, requested_address, size, true)) {          // OS ignored requested address. Try different address.          return;        }        // Check alignment constraints        assert((uintptr_t) base % alignment == 0, "Large pages returned a non-aligned address");        _special = true; 

  (3).若配置了UseSharedSpace或UseCompressedOops,那么堆將在指定地址進行申請,就會調用attempt_reserve_memory_at()進行申請,否則,調用reserve_memory()進行申請

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);      }  

  (4).若分配成功,還需要對分配的起始地址進行對齊驗證。若沒有對齊,則會進行手工調整。調整的方法為嘗試申請一塊size+alignment大小的空間,若成功則向上對齊所得的內存空間的起始地址(失敗則無法對齊,直接返回),并以此為起始地址重新申請一塊size大小的空間,這塊size大小的空間必然包含于size+alignment大小的空間內,以此達到對齊地址的目的。

// Check alignment constraints      if ((((size_t)base + noaccess_prefix) & (alignment - 1)) != 0) {        // Base not aligned, retry        if (!os::release_memory(base, size)) fatal("os::release_memory failed");        // Reserve size large enough to do manual alignment and        // increase size to a multiple of the desired alignment        size = align_size_up(size, alignment);        size_t extra_size = size + alignment;        do {          char* extra_base = os::reserve_memory(extra_size, NULL, alignment);          if (extra_base == NULL) return;          // Do manual alignement          base = (char*) align_size_up((uintptr_t) extra_base, alignment);          assert(base >= extra_base, "just checking");          // Re-reserve the region at the aligned base address.          os::release_memory(extra_base, extra_size);          base = os::reserve_memory(size, base);        } while (base == NULL); 

  最后,在地址空間均已分配完畢,GenCollectedHeap的initialize()中為各代劃分了各自的內存空間范圍,就會調用各代的GenerationSpec的init()函數完成各代的初始化。

switch (name()) {    case PermGen::MarkSweepCompact:      return new CompactingPermGen(perm_rs, shared_rs, init_size, remset, this);#ifndef SERIALGC    case PermGen::MarkSweep:      guarantee(false, "NYI");      return NULL;    case PermGen::ConcurrentMarkSweep: {      assert(UseConcMarkSweepGC, "UseConcMarkSweepGC should be set");      CardTableRS* ctrs = remset->as_CardTableRS();      if (ctrs == NULL) {        vm_exit_during_initialization("RemSet/generation incompatibility.");      }      // XXXPERM      return new CMSPermGen(perm_rs, init_size, ctrs,                   (FreeBlockDictionary::DictionaryChoice)CMSDictionaryChoice);    }#endif // SERIALGC    default:      guarantee(false, "unrecognized GenerationName");      return NULL;  }

 各分代實現類的類關系如下: 

歸納堆初始化的流程圖如下: 

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 昌平区| 疏附县| 芷江| 伊春市| 施秉县| 龙里县| 广平县| 永胜县| 南汇区| 弥勒县| 长海县| 苏尼特右旗| 特克斯县| 乌拉特中旗| 宜阳县| 营山县| 革吉县| 剑河县| 蛟河市| 鹿泉市| 白银市| 台北市| 永福县| 南宁市| 梨树县| 临颍县| 绥江县| 略阳县| 女性| 大兴区| 阿巴嘎旗| 德惠市| 六枝特区| 乐昌市| 舞阳县| 扎赉特旗| 潮州市| 吐鲁番市| 申扎县| 仁寿县| 梅州市|