內存代管理器TenuredGeneration作爲基於內存分代管理的內存堆管理器GenCollectedHeap默認的舊生代管理器,它對垃圾對象的回收算法要比年青代Gc要複雜的多,但其主體思路就是:標記-清除-壓縮.本文主要圍繞內存代Gc時的三個過程來詳細講解TenuredGeneration是如何進行垃圾回收的.
一.Gc條件
可對 TenuredGeneration管理的舊生代進行Gc的條件主要有4個,滿足其中的任何一個即可對舊生代進行垃圾回收:
1.當前是Full Gc
2.可在當前內存代分配請求的空間
3.當前內存代空閒空間<10000
4.當前內存代容量>上一次Gc之前的容量
具體代碼可參見如下:
bool TenuredGeneration::should_collect(bool full, size_t size, bool is_tlab) {
// This should be one big conditional or (||), but I want to be able to tell
// why it returns what it returns (without re-evaluating the conditionals
// in case they aren't idempotent), so I'm doing it this way.
// DeMorgan says it's okay.
bool result = false;
if (!result && full) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because full");
}
}
if (!result && should_allocate(size, is_tlab)) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
" should_allocate(" SIZE_FORMAT ")", size);
}
}
// If we don't have very much free space.
// XXX: 10000 should be a percentage of the capacity!!!
if (!result && free() < 10000) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
" free(): " SIZE_FORMAT,
free());
}
}
// If we had to expand to accomodate promotions from younger generations
if (!result && _capacity_at_prologue < capacity()) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
"_capacity_at_prologue: " SIZE_FORMAT " < capacity(): " SIZE_FORMAT,
_capacity_at_prologue, capacity());
}
}
return result;
}
二.Gc基本流程
默認的舊生代管理器TenuredGeneration回收垃圾對象的基本思路就是:
第一步: 標記所有的active對象
第二步: 計算所有active對象在其內存代壓縮後的偏移位置
第三步: 更新所有active對象的地址映射表
第四步: 移動複製所有的active對象到新的存儲位置
1.標記所有的active對象
TenuredGeneration標記所有active對象的過程跟年青代Gc相似,都是從根對象開始以深度優先的方式搜索標記所有的active對象,具體過程如下:
/**
* 從根對象開始遞歸迭代標記所有活動的對象
*/
void GenMarkSweep::mark_sweep_phase1(int level,
bool clear_all_softrefs) {
// Recursively traverse all live objects and mark them
TraceTime tm("phase 1", PrintGC && Verbose, true, gclog_or_tty);
trace(" 1");
VALIDATE_MARK_SWEEP_ONLY(reset_live_oop_tracking(false));
GenCollectedHeap* gch = GenCollectedHeap::heap();
// Because follow_root_closure is created statically, cannot
// use OopsInGenClosure constructor which takes a generation,
// as the Universe has not been created when the static constructors
// are run.
follow_root_closure.set_orig_generation(gch->get_gen(level));
/**
* 遍歷當前所有的根對象並標記,並遞歸遍歷標記它們的引用對象
*/
gch->gen_process_strong_roots(level,
false, // Younger gens are not roots.
true, // activate StrongRootsScope
true, // Collecting permanent generation.
SharedHeap::SO_SystemClasses,
&follow_root_closure,
true, // walk code active on stacks
&follow_root_closure);
//標記所有的軟引用對象
{
ref_processor()->setup_policy(clear_all_softrefs);
ref_processor()->process_discovered_references(&is_alive, &keep_alive, &follow_stack_closure, NULL);
}
// Follow system dictionary roots and unload classes
bool purged_class = SystemDictionary::do_unloading(&is_alive);
// 清理代碼高速緩衝區
CodeCache::do_unloading(&is_alive, &keep_alive, purged_class);
follow_stack(); // Flush marking stack
// Update subklass/sibling/implementor links of live klasses
follow_weak_klass_links();
assert(_marking_stack.is_empty(), "just drained");
//清理未被標記的軟/弱引用對象
follow_mdo_weak_refs();
assert(_marking_stack.is_empty(), "just drained");
//清除沒有被引用的常量字符串
StringTable::unlink(&is_alive);
//清除符號表中沒有被引用的符號
SymbolTable::unlink();
assert(_marking_stack.is_empty(), "stack should be empty by now");
}
2.計算所有active對象在其內存代壓縮後的偏移位置
以內存代爲單位計算各內存代中的active對象壓縮後的新存儲位置:
/**
* 通過對象的標記,計算存活的對象在其內存區壓縮後的偏移位置
*/
void GenMarkSweep::mark_sweep_phase2() {
// Now all live objects are marked, compute the new object addresses.
// It is imperative that we traverse perm_gen LAST. If dead space is
// allowed a range of dead object may get overwritten by a dead int
// array. If perm_gen is not traversed last a klassOop may get
// overwritten. This is fine since it is dead, but if the class has dead
// instances we have to skip them, and in order to find their size we
// need the klassOop!
//
// It is not required that we traverse spaces in the same order in
// phase2, phase3 and phase4, but the ValidateMarkSweep live oops
// tracking expects us to do so. See comment under phase4.
GenCollectedHeap* gch = GenCollectedHeap::heap();
Generation* pg = gch->perm_gen();
TraceTime tm("phase 2", PrintGC && Verbose, true, gclog_or_tty);
trace("2");
VALIDATE_MARK_SWEEP_ONLY(reset_live_oop_tracking(false));
//計算年青代+老生代中active對象在其內存代壓縮後的偏移位置
gch->prepare_for_compaction();
VALIDATE_MARK_SWEEP_ONLY(_live_oops_index_at_perm = _live_oops_index);
//計算永久代中active對象在其內存代壓縮後的偏移位置
CompactPoint perm_cp(pg, NULL, NULL);
pg->prepare_for_compaction(&perm_cp);
}
在內存代內部,又是以內存區爲單位來計算的:
/**
* 計算內存代放入各個內存區中活着的對象在其壓縮後的偏移位置
*/
void Generation::prepare_for_compaction(CompactPoint* cp) {
// Generic implementation, can be specialized
CompactibleSpace* space = first_compaction_space();
while (space != NULL) {
space->prepare_for_compaction(cp);
space = space->next_compaction_space();
}
}
內存區的計算方法是(對象新的存儲地址存放在對象實例的_makr屬性中):
/**
* 計算內存區中所有活着(被標記)的對象在其被壓縮後的偏移位置
*/
void CompactibleSpace::prepare_for_compaction(CompactPoint* cp) {
SCAN_AND_FORWARD(cp, end, block_is_obj, block_size);
}
#define SCAN_AND_FORWARD(cp,scan_limit,block_is_obj,block_size) { \
/* Compute the new addresses for the live objects and store it in the mark \
* Used by universe::mark_sweep_phase2() \
*/ \
HeapWord* compact_top; /* This is where we are currently compacting to. */ \
\
/* We're sure to be here before any objects are compacted into this \
* space, so this is a good time to initialize this: \
*/ \
set_compaction_top(bottom()); \
\
if (cp->space == NULL) { \
assert(cp->gen != NULL, "need a generation"); \
assert(cp->threshold == NULL, "just checking"); \
assert(cp->gen->first_compaction_space() == this, "just checking"); \
\
cp->space = cp->gen->first_compaction_space(); \
compact_top = cp->space->bottom(); \
cp->space->set_compaction_top(compact_top); \
cp->threshold = cp->space->initialize_threshold(); \
} else { \
compact_top = cp->space->compaction_top(); \
} \
\
/* We allow some amount of garbage towards the bottom of the space, so \
* we don't start compacting before there is a significant gain to be made.\
* Occasionally, we want to ensure a full compaction, which is determined \
* by the MarkSweepAlwaysCompactCount parameter. \
*/ \
int invocations = SharedHeap::heap()->perm_gen()->stat_record()->invocations;\
bool skip_dead = (MarkSweepAlwaysCompactCount < 1) \
||((invocations % MarkSweepAlwaysCompactCount) != 0); \
\
size_t allowed_deadspace = 0; \
if (skip_dead) { \
const size_t ratio = allowed_dead_ratio(); \
allowed_deadspace = (capacity() * ratio / 100) / HeapWordSize; \
} \
\
/** \
* 當前內存區壓縮的開始位置和結束位置 \
*/ \
HeapWord* q = bottom(); \
HeapWord* t = scan_limit(); \
\
HeapWord* end_of_live= q; /* One byte beyond the last byte of the last \
live object. */ \
HeapWord* first_dead = end();/* The first dead object. */ \
LiveRange* liveRange = NULL; /* The current live range, recorded in the \
first header of preceding free area. */ \
_first_dead = first_dead; /*當前內存區中最後一個active對象*/ \
\
const intx interval = PrefetchScanIntervalInBytes; \
\
/** \
* 開始遍歷該內存區分配的所有對象 \
*/ \
while (q < t) { \
assert(!block_is_obj(q) || \
oop(q)->mark()->is_marked() || oop(q)->mark()->is_unlocked() || \
oop(q)->mark()->has_bias_pattern(), \
"these are the only valid states during a mark sweep"); \
/** \
* active對象,則確定其新的存儲位置 \
*/ \
if (block_is_obj(q) && oop(q)->is_gc_marked()) { \
/* prefetch beyond q */ \
Prefetch::write(q, interval); \
/* size_t size = oop(q)->size(); changing this for cms for perm gen */\
/*對象大小*/ \
size_t size = block_size(q); \
compact_top = cp->space->forward(oop(q), size, cp, compact_top); \
q += size; \
end_of_live = q; \
} else { \
/* 垃圾對象,則一直遍歷到其下一個active對象 */ \
HeapWord* end = q; \
do { \
/* prefetch beyond end */ \
Prefetch::write(end, interval); \
end += block_size(end); \
} while (end < t && (!block_is_obj(end) || !oop(end)->is_gc_marked()));\
\
/* see if we might want to pretend this object is alive so that \
* we don't have to compact quite as often. \
*/ \
if (allowed_deadspace > 0 && q == compact_top) { \
size_t sz = pointer_delta(end, q); \
if (insert_deadspace(allowed_deadspace, q, sz)) { \
compact_top = cp->space->forward(oop(q), sz, cp, compact_top); \
q = end; \
end_of_live = end; \
continue; \
} \
} \
\
/* otherwise, it really is a free region. */ \
\
/* for the previous LiveRange, record the end of the live objects. */ \
if (liveRange) { \
liveRange->set_end(q); \
} \
\
/* record the current LiveRange object. \
* liveRange->start() is overlaid on the mark word. \
*/ \
liveRange = (LiveRange*)q; \
liveRange->set_start(end); \
liveRange->set_end(end); \
\
/* see if this is the first dead region. */ \
if (q < first_dead) { \
first_dead = q; \
} \
\
/* move on to the next object */ \
q = end; \
} \
} \
\
assert(q == t, "just checking"); \
if (liveRange != NULL) { \
liveRange->set_end(q); \
} \
_end_of_live = end_of_live; \
if (end_of_live < first_dead) { \
first_dead = end_of_live; \
} \
_first_dead = first_dead; \
\
/* save the compaction_top of the compaction space. */ \
cp->space->set_compaction_top(compact_top); \
}
3.更新所有active對象的地址映射表
該過程的實現和標記所有的active對象很相似,就是一個是標記,一個是更新地址指針:
/**
* 從根對象開始遞歸迭代更新所有活動對象的地址映射表(調整所有的對象指針)
*/
void GenMarkSweep::mark_sweep_phase3(int level) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
Generation* pg = gch->perm_gen();
// Adjust the pointers to reflect the new locations
TraceTime tm("phase 3", PrintGC && Verbose, true, gclog_or_tty);
trace("3");
VALIDATE_MARK_SWEEP_ONLY(reset_live_oop_tracking(false));
// Needs to be done before the system dictionary is adjusted.
pg->pre_adjust_pointers();
// Because the two closures below are created statically, cannot
// use OopsInGenClosure constructor which takes a generation,
// as the Universe has not been created when the static constructors
// are run.
adjust_root_pointer_closure.set_orig_generation(gch->get_gen(level));
adjust_pointer_closure.set_orig_generation(gch->get_gen(level));
gch->gen_process_strong_roots(level,
false, // Younger gens are not roots.
true, // activate StrongRootsScope
true, // Collecting permanent generation.
SharedHeap::SO_AllClasses,
&adjust_root_pointer_closure,
false, // do not walk code
&adjust_root_pointer_closure);
// Now adjust pointers in remaining weak roots. (All of which should
// have been cleared if they pointed to non-surviving objects.)
CodeBlobToOopClosure adjust_code_pointer_closure(&adjust_pointer_closure,
/*do_marking=*/ false);
gch->gen_process_weak_roots(&adjust_root_pointer_closure,
&adjust_code_pointer_closure,
&adjust_pointer_closure);
adjust_marks();
GenAdjustPointersClosure blk;
gch->generation_iterate(&blk, true);
pg->adjust_pointers();
}
這裏的對象地址映射表實際上就是前面的博文內存堆Gc時公認的根對象
中提到的實例對象句柄.
/**
* 調整對象的物理地址指針
*/
template <class T> inline void MarkSweep::adjust_pointer(T* p, bool isroot) {
T heap_oop = oopDesc::load_heap_oop(p);
if (!oopDesc::is_null(heap_oop)) {
oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);
oop new_obj = oop(obj->mark()->decode_pointer()); //對象新的地址指針
assert(new_obj != NULL || // is forwarding ptr?
obj->mark() == markOopDesc::prototype() || // not gc marked?
(UseBiasedLocking && obj->mark()->has_bias_pattern()) ||
// not gc marked?
obj->is_shared(), // never forwarded?
"should be forwarded");
if (new_obj != NULL) {
assert(Universe::heap()->is_in_reserved(new_obj),
"should be in object space");
//更新對象的物理地址指針
oopDesc::encode_store_heap_oop_not_null(p, new_obj);
}
}
VALIDATE_MARK_SWEEP_ONLY(track_adjusted_pointer(p, isroot));
}
4.移動複製所有的active對象到新的存儲位置
該過程跟計算所有active對象在其內存代壓縮後的偏移位置的操作流程很相似,這裏不在詳細贅述.
三.Gc後的內存代大小調整
內存堆管理器在對某些內存代進行Gc之後,基本都會調整他們的內存容量,即對內存代進行擴容還是縮容.這個操作主要受控於兩個參數:最小空閒率(MinHeapFreeRatio)和最大空閒率MaxHeapFreeRatio.TenuredGeneration根據這兩個參數來調整舊生代的容量過程如下:
/**
* 調整當前內存代的容量(一般發生在一次Gc之後)
*/
void TenuredGeneration::compute_new_size() {
assert(_shrink_factor <= 100, "invalid shrink factor");
size_t current_shrink_factor = _shrink_factor;
_shrink_factor = 0;
printf("%s[%d] [tid: %lu]: 當前內存代[%s]開始調整其容量大小.\n", __FILE__, __LINE__, pthread_self(), this->name());
// We don't have floating point command-line arguments
// Note: argument processing ensures that MinHeapFreeRatio < 100.
const double minimum_free_percentage = MinHeapFreeRatio / 100.0; //內存代的最小空閒率
const double maximum_used_percentage = 1.0 - minimum_free_percentage;
//一次Gc之後,當前內存代的使用量和容量
const size_t used_after_gc = used();
const size_t capacity_after_gc = capacity();
//計算當前內存代期望的最小容量
const double min_tmp = used_after_gc / maximum_used_percentage;
size_t minimum_desired_capacity = (size_t)MIN2(min_tmp, double(max_uintx));
// Don't shrink less than the initial generation size
minimum_desired_capacity = MAX2(minimum_desired_capacity, spec()->init_size());
assert(used_after_gc <= minimum_desired_capacity, "sanity check");
if (PrintGC && Verbose) {
const size_t free_after_gc = free();
const double free_percentage = ((double)free_after_gc) / capacity_after_gc;
gclog_or_tty->print_cr("TenuredGeneration::compute_new_size: ");
gclog_or_tty->print_cr(" "
" minimum_free_percentage: %6.2f"
" maximum_used_percentage: %6.2f",
minimum_free_percentage,
maximum_used_percentage);
gclog_or_tty->print_cr(" "
" free_after_gc : %6.1fK"
" used_after_gc : %6.1fK"
" capacity_after_gc : %6.1fK",
free_after_gc / (double) K,
used_after_gc / (double) K,
capacity_after_gc / (double) K);
gclog_or_tty->print_cr(" "
" free_percentage: %6.2f",
free_percentage);
}
//當前內存代的實際容量小於期望的容量,則擴展當前內存代的容量
if (capacity_after_gc < minimum_desired_capacity) {
// If we have less free space than we want then expand
size_t expand_bytes = minimum_desired_capacity - capacity_after_gc;
// Don't expand unless it's significant
if (expand_bytes >= _min_heap_delta_bytes) {
printf("%s[%d] [tid: %lu]: Gc之後,試圖爲內存代[%s]的容量擴大 %lu bytes.\n", __FILE__, __LINE__, pthread_self(), this->name(), expand_bytes);
expand(expand_bytes, 0); // safe if expansion fails
}
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(" expanding:"
" minimum_desired_capacity: %6.1fK"
" expand_bytes: %6.1fK"
" _min_heap_delta_bytes: %6.1fK",
minimum_desired_capacity / (double) K,
expand_bytes / (double) K,
_min_heap_delta_bytes / (double) K);
}
return;
}
// No expansion, now see if we want to shrink
size_t shrink_bytes = 0;
// We would never want to shrink more than this
size_t max_shrink_bytes = capacity_after_gc - minimum_desired_capacity;
if (MaxHeapFreeRatio < 100) {
//計算當前內存代期望的大容量
const double maximum_free_percentage = MaxHeapFreeRatio / 100.0;
const double minimum_used_percentage = 1.0 - maximum_free_percentage;
const double max_tmp = used_after_gc / minimum_used_percentage;
size_t maximum_desired_capacity = (size_t)MIN2(max_tmp, double(max_uintx));
maximum_desired_capacity = MAX2(maximum_desired_capacity, spec()->init_size());
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(" "
" maximum_free_percentage: %6.2f"
" minimum_used_percentage: %6.2f",
maximum_free_percentage,
minimum_used_percentage);
gclog_or_tty->print_cr(" "
" _capacity_at_prologue: %6.1fK"
" minimum_desired_capacity: %6.1fK"
" maximum_desired_capacity: %6.1fK",
_capacity_at_prologue / (double) K,
minimum_desired_capacity / (double) K,
maximum_desired_capacity / (double) K);
}
assert(minimum_desired_capacity <= maximum_desired_capacity,
"sanity check");
//當前內存代Gc之後的容量大於期望的最大容量
if (capacity_after_gc > maximum_desired_capacity) {
// Capacity too large, compute shrinking size
shrink_bytes = capacity_after_gc - maximum_desired_capacity;
// We don't want shrink all the way back to initSize if people call
// System.gc(), because some programs do that between "phases" and then
// we'd just have to grow the heap up again for the next phase. So we
// damp the shrinking: 0% on the first call, 10% on the second call, 40%
// on the third call, and 100% by the fourth call. But if we recompute
// size without shrinking, it goes back to 0%.
shrink_bytes = shrink_bytes / 100 * current_shrink_factor;
assert(shrink_bytes <= max_shrink_bytes, "invalid shrink size");
if (current_shrink_factor == 0) {
_shrink_factor = 10;
} else {
_shrink_factor = MIN2(current_shrink_factor * 4, (size_t) 100);
}
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(" "
" shrinking:"
" initSize: %.1fK"
" maximum_desired_capacity: %.1fK",
spec()->init_size() / (double) K,
maximum_desired_capacity / (double) K);
gclog_or_tty->print_cr(" "
" shrink_bytes: %.1fK"
" current_shrink_factor: %d"
" new shrink factor: %d"
" _min_heap_delta_bytes: %.1fK",
shrink_bytes / (double) K,
current_shrink_factor,
_shrink_factor,
_min_heap_delta_bytes / (double) K);
}
}
}
if (capacity_after_gc > _capacity_at_prologue) {
//當前內存代Gc之後的容量大於Gc之前的容量,那麼就是在內存代Gc時爲了存儲升級來的active對象而擴展了內存容量,
//現在至少應該縮小到Gc之前的容量大小
size_t expansion_for_promotion = capacity_after_gc - _capacity_at_prologue;
expansion_for_promotion = MIN2(expansion_for_promotion, max_shrink_bytes);
// We have two shrinking computations, take the largest
shrink_bytes = MAX2(shrink_bytes, expansion_for_promotion);
assert(shrink_bytes <= max_shrink_bytes, "invalid shrink size");
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(" "
" aggressive shrinking:"
" _capacity_at_prologue: %.1fK"
" capacity_after_gc: %.1fK"
" expansion_for_promotion: %.1fK"
" shrink_bytes: %.1fK",
capacity_after_gc / (double) K,
_capacity_at_prologue / (double) K,
expansion_for_promotion / (double) K,
shrink_bytes / (double) K);
}
}
// Don't shrink unless it's significant
if (shrink_bytes >= _min_heap_delta_bytes) {
printf("%s[%d] [tid: %lu]: Gc之後,試圖爲內存代[%s]的容量縮小 %lu bytes.\n", __FILE__, __LINE__, pthread_self(), this->name(), shrink_bytes);
shrink(shrink_bytes);
}
assert(used() == used_after_gc && used_after_gc <= capacity(),
"sanity check");
}