17.4 Java堆空間內(nèi)存分配
分配Java堆內(nèi)存前,我們先通過兩圖來了解下C堆、Java堆、內(nèi)核空間、native本地空間的關(guān)系。
1、從圖17-1來看,Java堆的分配其實就是從Java進程運行時堆中選中一塊內(nèi)存區(qū)域來映射
2、從圖17-2,可以看中各內(nèi)存空間的關(guān)系,當然實際的內(nèi)存區(qū)域比這個復雜的多,這里只是概括說明
圖17-1
圖17-2
17.4.1 genCollectedHeap.cpp
17.4.1.1 GenCollectedHeap::initialize
jint GenCollectedHeap::initialize() {
// 這一步只是對c2編譯器開通使用時,做一些參數(shù)賦值操作,這里就不展開講
CollectedHeap::pre_initialize();
// 這里獲取分代數(shù)_n_gens,就是2
int i;
_n_gens = gen_policy()->number_of_generations();
// 保證2個值相等wordSize和HeapWordSize分別是在操作系統(tǒng)和Java堆中代表一個字word占用內(nèi)存的大小,這兩個值必然相同,否則出錯
guarantee(HeapWordSize == wordSize, "HeapWordSize must equal wordSize");
// Java堆的對齊值,這個在`章節(jié)17.2.1.1`中有介紹
size_t gen_alignment = Generation::GenGrain;
// 獲取分代對象數(shù)組,這個在`章節(jié)17.2.1.1`中有介紹,數(shù)組元素就2個,索引0元素表示年輕代,索引1元素表示老年代
_gen_specs = gen_policy()->generations();
// 分別遍歷新生代和老年代,并設(shè)置各自分代的空間大?。ǔ跏贾岛妥畲笾担?,同時確保內(nèi)存對齊
for (i = 0; i < _n_gens; i++) {
_gen_specs[i]->align(gen_alignment);
}
// 下面才是給Java堆分配空間
char* heap_address;
size_t total_reserved = 0;
int n_covered_regions = 0;
ReservedSpace heap_rs;
// 這是最外層Java堆的內(nèi)存對齊值
size_t heap_alignment = collector_policy()->heap_alignment();
// 分配java堆內(nèi)存,看`章節(jié)17.4.1.2`
heap_address = allocate(heap_alignment, &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;
}
// 將分配的Java堆內(nèi)存,用 MemRegion 內(nèi)存區(qū)域?qū)ο蠊芾砥饋? _reserved = MemRegion((HeapWord*)heap_rs.base(),
(HeapWord*)(heap_rs.base() + heap_rs.size()));
// 參數(shù)賦值
_reserved.set_word_size(0);
_reserved.set_start((HeapWord*)heap_rs.base()); // Java堆內(nèi)存的首地址
size_t actual_heap_size = heap_rs.size(); // Java堆內(nèi)存大小
// Java堆內(nèi)存的限制地址,也就是不能超過這條線
_reserved.set_end((HeapWord*)(heap_rs.base() + actual_heap_size));
// 接下來就是創(chuàng)建記憶集、卡表的過程,卡表和記憶集都是為了解決跨代引用的實現(xiàn)方案,后續(xù)講GC時會有涉及
_rem_set = collector_policy()->create_rem_set(_reserved, n_covered_regions);
set_barrier_set(rem_set()->bs());
_gch = this;
for (i = 0; i < _n_gens; i++) {
ReservedSpace this_rs = heap_rs.first_part(_gen_specs[i]->max_size(), false, false);
_gens[i] = _gen_specs[i]->init(this_rs, i, rem_set());
heap_rs = heap_rs.last_part(_gen_specs[i]->max_size());
}
clear_incremental_collection_failed();
#if INCLUDE_ALL_GCS
// If we are running CMS, create the collector responsible
// for collecting the CMS generations.
if (collector_policy()->is_concurrent_mark_sweep_policy()) {
bool success = create_cms_collector();
if (!success) return JNI_ENOMEM;
}
#endif // INCLUDE_ALL_GCS
return JNI_OK;
}
17.4.1.2 GenCollectedHeap::allocate
char* GenCollectedHeap::allocate(size_t alignment,
size_t* _total_reserved,
int* _n_covered_regions,
ReservedSpace* heap_rs){
const char overflow_msg[] = "The size of the object heap + VM data exceeds "
"the maximum representable size";
// 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");
// 遍歷_gen_specs,求得新生代和老年代的分配大小
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(); // 最終為2
}
assert(total_reserved % alignment == 0,
err_msg("Gen size; total_reserved=" SIZE_FORMAT ", alignment="
SIZE_FORMAT, total_reserved, alignment));
// Needed until the cardtable is fixed to have the right number
// of covered regions.
n_covered_regions += 2; // 再加2,就是4,也就是把堆最終分成4個區(qū)(新生代、S1、S2、老年代)
*_total_reserved = total_reserved;
*_n_covered_regions = n_covered_regions;
// 分配內(nèi)存,實現(xiàn)細節(jié)看`章節(jié)17.4.2`
*heap_rs = Universe::reserve_heap(total_reserved, alignment);
return heap_rs->base();
}
17.4.2 universe.cpp
17.4.2.1 Universe::reserve_heap
ReservedSpace Universe::reserve_heap(size_t heap_size, size_t alignment) {
assert(alignment <= Arguments::conservative_max_heap_alignment(),
err_msg("actual alignment " SIZE_FORMAT " must be within maximum heap alignment " SIZE_FORMAT,
alignment, Arguments::conservative_max_heap_alignment()));
// 通過內(nèi)存對齊,得到要分配的空間大小
size_t total_reserved = align_size_up(heap_size, alignment);
assert(!UseCompressedOops || (total_reserved <= (OopEncodingHeapMax - os::vm_page_size())),
"heap size is too big for compressed oops");
// 大頁時考慮,本系列文章中不考慮大而情況,忽略
bool use_large_pages = UseLargePages && is_size_aligned(alignment, os::large_page_size());
assert(!UseLargePages
|| UseParallelGC
|| use_large_pages, "Wrong alignment to use large pages");
// 取出Java堆的基址base的值,32位機器時,就是0,實現(xiàn)細節(jié)看`章節(jié)17.4.2.2`
char* addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::UnscaledNarrowOop);
// 創(chuàng)建一個ReservedHeapSpace對象,該對象就是用來保留連續(xù)內(nèi)存地址范圍空間的數(shù)據(jù)結(jié)構(gòu),實現(xiàn)細節(jié)看`章節(jié)17.4.3`
ReservedHeapSpace total_rs(total_reserved, alignment, use_large_pages, addr);
if (UseCompressedOops) {
if (addr != NULL && !total_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.
addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::ZeroBasedNarrowOop);
ReservedHeapSpace total_rs0(total_reserved, alignment,
use_large_pages, addr);
if (addr != NULL && !total_rs0.is_reserved()) {
// Failed to reserve at specified address again - give up.
addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::HeapBasedNarrowOop);
assert(addr == NULL, "");
ReservedHeapSpace total_rs1(total_reserved, alignment,
use_large_pages, addr);
total_rs = total_rs1;
} else {
total_rs = total_rs0;
}
}
}
if (!total_rs.is_reserved()) {
vm_exit_during_initialization(err_msg("Could not reserve enough space for " SIZE_FORMAT "KB object heap", total_reserved/K));
return total_rs;
}
if (UseCompressedOops) {
// Universe::initialize_heap() will reset this to NULL if unscaled
// or zero-based narrow oops are actually used.
address base = (address)(total_rs.base() - os::vm_page_size());
Universe::set_narrow_oop_base(base);
}
// 返回total_rs
return total_rs;
}
17.4.2.2 Universe::preferred_heap_base
char* Universe::preferred_heap_base(size_t heap_size, size_t alignment, NARROW_OOP_MODE mode) {
assert(is_size_aligned((size_t)OopEncodingHeapMax, alignment), "Must be");
assert(is_size_aligned((size_t)UnscaledOopHeapMax, alignment), "Must be");
assert(is_size_aligned(heap_size, alignment), "Must be");
// HeapBaseMinAddress 是操作系統(tǒng)明確設(shè)定的堆內(nèi)存的最低地址限制,默認設(shè)置的是2*G,這里按alignment對齊,把HeapBaseMinAddress的值按alignment對齊后,作為堆內(nèi)存的最低地址
uintx heap_base_min_address_aligned = align_size_up(HeapBaseMinAddress, alignment);
size_t base = 0;
#ifdef _LP64 // 下面是對64位機器及使用壓縮指針時的實現(xiàn),我們只講32位的,這塊邏輯略過
if (UseCompressedOops) {
assert(mode == UnscaledNarrowOop ||
mode == ZeroBasedNarrowOop ||
mode == HeapBasedNarrowOop, "mode is invalid");
const size_t total_size = heap_size + heap_base_min_address_aligned;
// Return specified base for the first request.
if (!FLAG_IS_DEFAULT(HeapBaseMinAddress) && (mode == UnscaledNarrowOop)) {
base = heap_base_min_address_aligned;
// If the total size is small enough to allow UnscaledNarrowOop then
// just use UnscaledNarrowOop.
} else if ((total_size <= OopEncodingHeapMax) && (mode != HeapBasedNarrowOop)) {
if ((total_size <= UnscaledOopHeapMax) && (mode == UnscaledNarrowOop) &&
(Universe::narrow_oop_shift() == 0)) {
// Use 32-bits oops without encoding and
// place heap's top on the 4Gb boundary
base = (UnscaledOopHeapMax - heap_size);
} else {
// Can't reserve with NarrowOopShift == 0
Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
if (mode == UnscaledNarrowOop ||
mode == ZeroBasedNarrowOop && total_size <= UnscaledOopHeapMax) {
// Use zero based compressed oops with encoding and
// place heap's top on the 32Gb boundary in case
// total_size > 4Gb or failed to reserve below 4Gb.
uint64_t heap_top = OopEncodingHeapMax;
// For small heaps, save some space for compressed class pointer
// space so it can be decoded with no base.
if (UseCompressedClassPointers && !UseSharedSpaces &&
OopEncodingHeapMax <= 32*G) {
uint64_t class_space = align_size_up(CompressedClassSpaceSize, alignment);
assert(is_size_aligned((size_t)OopEncodingHeapMax-class_space,
alignment), "difference must be aligned too");
uint64_t new_top = OopEncodingHeapMax-class_space;
if (total_size <= new_top) {
heap_top = new_top;
}
}
// Align base to the adjusted top of the heap
base = heap_top - heap_size;
}
}
} else {
// UnscaledNarrowOop encoding didn't work, and no base was found for ZeroBasedOops or
// HeapBasedNarrowOop encoding was requested. So, can't reserve below 32Gb.
Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
}
// Set narrow_oop_base and narrow_oop_use_implicit_null_checks
// used in ReservedHeapSpace() constructors.
// The final values will be set in initialize_heap() below.
if ((base != 0) && ((base + heap_size) <= OopEncodingHeapMax)) {
// Use zero based compressed oops
Universe::set_narrow_oop_base(NULL);
// Don't need guard page for implicit checks in indexed
// addressing mode with zero based Compressed Oops.
Universe::set_narrow_oop_use_implicit_null_checks(true);
} else {
// Set to a non-NULL value so the ReservedSpace ctor computes
// the correct no-access prefix.
// The final value will be set in initialize_heap() below.
Universe::set_narrow_oop_base((address)UnscaledOopHeapMax);
#if defined(_WIN64) || defined(AIX)
if (UseLargePages) {
// Cannot allocate guard pages for implicit checks in indexed
// addressing mode when large pages are specified on windows.
Universe::set_narrow_oop_use_implicit_null_checks(false);
}
#endif // _WIN64
}
}
#endif
assert(is_ptr_aligned((char*)base, alignment), "Must be");
// 最終返回base,在32位機器時,虛擬機就是返回0
return (char*)base; // also return NULL (don't care) for 32-bit VM
}
17.4.3 virtualspace.cpp
17.4.3.1 ReservedHeapSpace::ReservedHeapSpace
ReservedHeapSpace::ReservedHeapSpace(size_t size, size_t alignment,
bool large, char* requested_address) :
/* 先調(diào)用父類構(gòu)造函數(shù)
*/
ReservedSpace(size, alignment, large,
requested_address,
(UseCompressedOops && (Universe::narrow_oop_base() != NULL) &&
Universe::narrow_oop_use_implicit_null_checks()) ?
lcm(os::vm_page_size(), alignment) : 0) {
if (base() != NULL) {
MemTracker::record_virtual_memory_type((address)base(), mtJavaHeap);
}
// Only reserved space for the java heap should have a noaccess_prefix
// if using compressed oops.
protect_noaccess_prefix(size);
}
17.4.3.2 ReservedSpace::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);
}
17.4.3.3 ReservedSpace::initialize
入口函數(shù): ReservedHeapSpace total_rs(total_reserved, alignment, use_large_pages, addr);
參數(shù):
total_reserved 對應 size:空間大小
alignment 對應 alignment:內(nèi)存對齊值
use_large_pages 對應 large:這里不考慮大頁,就設(shè)置為false
addr 對應 requested_address:32位時,addr為0
noaccess_prefix 為 0文章來源:http://www.zghlxwxcb.cn/news/detail-822515.html
executable 為 false文章來源地址http://www.zghlxwxcb.cn/news/detail-822515.html
void ReservedSpace::initialize(size_t size, size_t alignment, bool large,
char* requested_address,
const size_t noaccess_prefix,
bool executable) {
// 看源碼得知,這里就是取page size(頁大小),沒什么邏輯
const size_t granularity = os::vm_allocation_granularity();
// 斷言檢驗
assert((size & (granularity - 1)) == 0,
"size not aligned to os::vm_allocation_granularity()");
assert((alignment & (granularity - 1)) == 0,
"alignment not aligned to os::vm_allocation_granularity()");
assert(alignment == 0 || is_power_of_2((intptr_t)alignment),
"not a power of 2");
// 取二者最大值對齊
alignment = MAX2(alignment, (size_t)os::vm_page_size());
// Assert that if noaccess_prefix is used, it is the same as alignment.
assert(noaccess_prefix == 0 ||
noaccess_prefix == alignment, "noaccess prefix wrong");
_base = NULL;
_size = 0;
_special = false;
_executable = executable;
_alignment = 0;
_noaccess_prefix = 0;
if (size == 0) {
return;
}
// 不存在大頁,special 為 false
bool special = large && !os::can_commit_large_page_memory();
char* base = NULL;
// 32位機器時 requested_address == 0,這條線也不會走
if (requested_address != 0) {
requested_address -= noaccess_prefix; // adjust requested address
assert(requested_address != NULL, "huge noaccess prefix?");
}
// special為false,這個if不會走
if (special) {
base = os::reserve_memory_special(size, alignment, 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,
err_msg("Large pages returned a non-aligned address, base: "
PTR_FORMAT " alignment: " PTR_FORMAT,
base, (void*)(uintptr_t)alignment));
_special = true;
} else {
// failed; try to reserve regular memory below
if (UseLargePages && (!FLAG_IS_DEFAULT(UseLargePages) ||
!FLAG_IS_DEFAULT(LargePageSizeInBytes))) {
if (PrintCompressedOopsMode) {
tty->cr();
tty->print_cr("Reserve regular memory without large pages.");
}
}
}
}
if (base == 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 {
// 這一步就是通過系統(tǒng)調(diào)用mmap映射一塊size大小的內(nèi)存,Java堆內(nèi)存就是mmap映射出來的
base = os::reserve_memory(size, NULL, alignment);
}
// 映射失敗,直接退出函數(shù),分配Java堆內(nèi)存失敗
if (base == NULL) return;
// 驗證對齊,為啥要驗證呢,因為base是mmap映射后返回的內(nèi)存首地址,這個地址是os自己的規(guī)則選取的一個地址,不一定能按照alignment對齊,所以這一定要驗證
if ((((size_t)base + noaccess_prefix) & (alignment - 1)) != 0) {
// base沒有對齊,只能釋放剛才mmap映射的內(nèi)存,然后重試
if (!os::release_memory(base, size)) fatal("os::release_memory failed");
// 確保對齊
size = align_size_up(size, alignment);
// 再次mmap映射內(nèi)存,返回的base同樣有上面一樣的不對齊問題,所以這個函數(shù)中包含了手動對齊操作,細節(jié)看`章節(jié)17.4.3.4`
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
_base = base; // 最終拿到了Java堆的首地址
_size = size; // 最終拿到了Java堆的大小
_alignment = alignment; // 對齊值
_noaccess_prefix = noaccess_prefix; // 0
// 斷言判斷
assert(noaccess_prefix == 0 ||
noaccess_prefix == _alignment, "noaccess prefix wrong");
assert(markOopDesc::encode_pointer_as_mark(_base)->decode_pointer() == _base,
"area must be distinguisable from marks for mark-sweep");
assert(markOopDesc::encode_pointer_as_mark(&_base[size])->decode_pointer() == &_base[size],
"area must be distinguisable from marks for mark-sweep");
}
17.4.3.4 os_posix.cpp->os::reserve_memory_aligned
char* os::reserve_memory_aligned(size_t size, size_t alignment) {
assert((alignment & (os::vm_allocation_granularity() - 1)) == 0,
"Alignment must be a multiple of allocation granularity (page size)");
assert((size & (alignment -1)) == 0, "size must be 'alignment' aligned");
size_t extra_size = size + alignment;
assert(extra_size >= size, "overflow, size is too large to allow alignment");
// mmap映射一塊內(nèi)存區(qū)域,返回首地址
char* extra_base = os::reserve_memory(extra_size, NULL, alignment);
if (extra_base == NULL) {
return NULL;
}
// 手動對齊
char* aligned_base = (char*) align_size_up((uintptr_t) extra_base, alignment);
// [ | | ]
// ^ extra_base
// ^ extra_base + begin_offset == aligned_base
// extra_base + begin_offset + size ^
// extra_base + extra_size ^
// |<>| == begin_offset
// end_offset == |<>|
// 用對齊后的地址-mmap的首地址,得出與首地址的偏移值
size_t begin_offset = aligned_base - extra_base;
// 結(jié)束地址對齊后的偏移
size_t end_offset = (extra_base + extra_size) - (aligned_base + size);
// begin_offset > 0,表示確實有偏移,那就把extra_base到偏移的這部分釋放掉,因為有新的首地址了
if (begin_offset > 0) {
os::release_memory(extra_base, begin_offset);
}
// end_offset > 0,表示確實有偏移,那就把end_offset偏移的這部分釋放掉,因為有新的限制地址了
if (end_offset > 0) {
os::release_memory(extra_base + begin_offset + size, end_offset);
}
// 返回首地址
return aligned_base;
}
到了這里,關(guān)于Hotspot源碼解析-第十七章-虛擬機萬物創(chuàng)建(三)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!