前言
我們每天都在與Java堆打交道岔留,對它的組成與調(diào)優(yōu)都有了比較深刻的理解庵佣。Java堆的簡單示意圖如下。
不過劝篷,你有沒有想過堆空間到底是怎么產(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í)行流程是:
- 如果JVM使用了并行收集器(
-XX:+UseParallelGC
),則將堆初始化為ParallelScavengeHeap類型桑涎,即并行收集堆彬向。 - 如果JVM使用了G1收集器(
-XX:+UseG1GC
),則將堆初始化為G1CollectedHeap類型攻冷,即G1堆娃胆。同時設置GC策略為G1專用的G1CollectorPolicy。 - 如果沒有選擇以上兩種收集器讲衫,就繼續(xù)檢查是否使用了串行收集器(
-XX:+UseSerialGC
)缕棵,如是,設置GC策略為MarkSweepPolicy涉兽,即標記-清除招驴。 - 再檢查到如果使用了CMS收集器(
-XX:+UseConcMarkSweepGC
),就根據(jù)是否啟用自適應開關(-XX:+UseAdaptiveSizePolicy
)枷畏,設置GC策略為自適應的ASConcurrentMarkSweepPolicy别厘,或者標準的ConcurrentMarkSweepPolicy。 - 如果以上情況都沒有配置拥诡,就采用默認的GC策略為MarkSweepPolicy触趴。對于步驟3~5的所有情況,都會將堆初始化為GenCollectedHeap類型渴肉,即分代收集堆冗懦。
- 調(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í)行流程如下,有點長:
- 確定當前的頁大小瘫俊。
- 根據(jù)新生代鹊杖、老生代和永久代的各個GenerationSpec悴灵,將它們的最大內(nèi)存大小累加到total_reserved變量,作為申請內(nèi)存的總量骂蓖。
- 同時將GenerationSpec中的n_covered_regions一同累加积瞒,該字段代表申請內(nèi)存區(qū)域的數(shù)量,新生代登下、老生代都為1茫孔,永久代為2。
- 如果配置為大頁模式被芳,將申請內(nèi)存的量向上對齊到頁大小缰贝。
- 若啟用了壓縮普通對象指針(
-XX:+UseCompressedOops
),調(diào)用Universe::preferred_heap_base()方法筐钟,以32位直接壓縮的方式(UnscaledNarrowOop)取得堆的基地址,并調(diào)用ReservedHeapSpace的構造方法赋朦,申請內(nèi)存篓冲。 - 如果上一步申請失敗,說明比4GB大宠哄,就以零基地址壓縮的方式(ZeroBasedNarrowOop)在更高的地址空間上取得堆的基地址并申請內(nèi)存壹将。
- 如果仍然申請失敗,說明比32GB還大毛嫉,就只能用普通的指針壓縮方式(HeapBasedNarrowOop)取得堆的基地址并申請內(nèi)存诽俯。
- 如果沒有啟用壓縮普通對象指針,就直接用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天谤牡,不想寫總結了副硅,晚安吧各位_(′?`」 ∠)_