虛擬機在內存中申請一片區域,由虛擬機自動管理,用來知足應用程序對象分配的空間需求,即堆空間。java
因爲程序運行的局部特性,程序建立的大多數對象都具備很是短的生命週期,而程序也會建立一些生命週期特別長的對象。簡單的複製收集器不管對象的生命週期是長是短,都會進行復制操做。而生命週期較長的對象在屢次垃圾回收期間內並不會被回收,這就使得這些對象被來回複製而使得算法性能大大降低。算法
分代收集把堆分爲多個子堆,分別用來存放不一樣壽命的對象。新生對象空間的將經歷最頻繁的垃圾回收,而對於經歷了若干次垃圾收集後仍然存活的對象,將成長爲成熟對象,並移動到成熟對象的子堆中,而對老生代子堆的垃圾回收就不會像新生對象子堆那麼頻繁。數組
HotSpot的堆空間分爲新生代(YoungGen)和老年代(OldGen,此外還有位於非堆空間的永久代,但在Java8中將移除永久代),新生代又分爲Eden區和2個Survivor區(From/To)用以進行復制收集垃圾對象。
對Java堆和對象的分析將從Java堆的建立開始,而後分析Java對象的分配與垃圾回收。併發
在虛擬機的建立初始化過程當中,經過調用Universe的成員函數initialize_heap()將完成Java堆的初始化。在Universe模塊下的初始化將根據虛擬機選項來選擇堆的具體實現方式:
1.若虛擬機配置UseParallelGC,則Java堆的堆類型爲ParallelScavengeHeap(並行收集堆)jvm
//定義在/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(分代收集堆)oop
Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
各個堆實現類的類關係以下:性能
對於默認狀況下的堆實現,還要根據配置選擇垃圾回收策略gc_policy來構造一個GenCollectedHeap,這裏根據虛擬機配置選擇不一樣的GC策略:
(1).若虛擬機配置UseSerialGC,那麼將使用MarkSweepPolicy(標記-清除)策略ui
GenCollectorPolicy *gc_policy; if (UseSerialGC) { gc_policy = new MarkSweepPolicy(); }
(2).若虛擬機配置UseConcMarkSweepGC和UseAdaptiveSizePolicy,那麼將使用ASConcurrentMarkSweepPolicy(自適應併發標記-清除)策略,若沒有指定UseAdaptiveSizePolicy,虛擬機將默認使用ConcurrentMarkSweepPolicy(併發標記-清除)策略this
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; }
各分代實現類的類關係以下:
概括堆初始化的流程圖以下: