上一篇《collector plicy》我们讲到了在不同配置下初始化不同的Generation子类,从本篇博客开始讲解Generation初始化过程中用到的用来表示一个内存区域如Eden区,From区和To区的Space及其相关子类的实现。
一、Space
Space及其子类是Generation中用于内存分配管理的底层实现,其定义在hotspot/src/share/vm/memory/space.hpp中,包含的属性如下:
HeapWord* _bottom; //内存区域的起始地址
HeapWord* _end; //内存区域的终止地址
HeapWord* _saved_mark_word; //
MemRegionClosure* _preconsumptionDirtyCardClosure; // 用来遍历Space内的MemRegion
SequentialSubTasksDone _par_seq_tasks;// 用来管控并发GC的多个线程
Space定义的方法中除属性相关的方法外,其他的方法大都是虚方法,重点关注其initialize方法的实现,如下:
void Space::initialize(MemRegion mr,
bool clear_space,
bool mangle_space){
//获取mr的起始地址
HeapWord* bottom = mr.start();
HeapWord* end = mr.end();
//校验起始地址都是按照内存页的大小内存对齐的
assert(Universe::on_page_boundary(bottom) && Universe::on_page_boundary(end),
"invalid space boundaries");
//设置属性
set_bottom(bottom);
set_end(end);
//初始化mr
if (clear_space) clear(mangle_space);
}
virtual void set_bottom(HeapWord* value) { _bottom = value; }
virtual void set_end(HeapWord* value) { _end = value; }
void Space::clear(bool mangle_space) {
//ZapUnusedHeapArea表示使用一个特定值填充不使用的内存块
if (ZapUnusedHeapArea && mangle_space) {
//mangle_unused_area是一个虚方法,Space中是空实现
mangle_unused_area();
}
}
virtual void mangle_unused_area() {}
其调用链如下:
image.png
从上述调用链也可知道不同Generation子类使用的Space子类,Space的类继承关系如下:
image.png
二、CompactibleSpace
CompactibleSpace继承自Space,同样在space.hpp中,增加了压缩操作的支持,即在垃圾回收后通过复制移动Java对象的位置减少Space内部的内存空洞和碎片问题,提升内存利用率。CompactibleSpace添加了如下属性:
- HeapWord* _compaction_top; //记录为compact而需要移动的对象分配的内存,即从bottom到_compaction_top之间的内存都已经被分配给那些需要移动的对象
- CompactibleSpace* _next_compaction_space; //下一个支持压缩操作的Space实例,如果当前Space实例没有足够的空间保存需要移动的对象了,就会切换到_next_compaction_space,在该Space实例中保存
- HeapWord* _first_dead; //第一个deadspace的起始地址,没有被标记的对象的内存区域或者非Java对象的内存区域都视为deadspace
- HeapWord* _end_of_live; //最后一个连续的被标记的活的的对象的内存区域的终止地址
- CompactibleSpace增加的方法都是压缩操作相关的,重点关注以下方法的实现。
1、prepare_for_compaction
prepare_for_compaction方法会遍历整个Space,找出所有被GC标记的活的对象,为后面的compact方法计算该对象需要移动的目标地址,该地址保存在对象头中。注意计算被移动到的目的地址时是从当前Space的bottom属性开始的,如果当前Space的剩余空间即_compaction_top到end之间的空间不足了则切换到当前Space的_next_compaction_space,切换过去后依然是从新的Space的bottom属性开始。即对象移动时优先在同一个Space内移动,因为移动时是按照对象地址从低到高的顺序依次被复制新地址上的,所以不用担心对象复制时会覆盖掉还未复制的对象的数据。对于其他的未被GC标记的对象的内存块或者非Java对象的内存块则统一视为deadspace,为了避免频繁的执行compact,可以将一定大小的连续的一段deadspace视为一个活的对象,同样计算其需要移动的目标地址。该方法的调用链如下:
image.png调用链中的GenMarkSweep就是GenCollectedHeap下执行标记并清理的核心,后续博客会详细介绍。该方法的源码如下:
void CompactibleSpace::prepare_for_compaction(CompactPoint* cp) {
SCAN_AND_FORWARD(cp, end, block_is_obj, block_size);
}
//scan_limit,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. */ \
\
//设置_compaction_top 为起始地址 \
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"); \
//first_compaction_space方法返回Generation中可以执行压缩的第一个Space
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.
* 允许执行一定次数的GC但是不执行压缩,只有有明显的收益才会执行压缩
* 偶尔情况,我们也会执行一个完全的压缩,这个是通过参数MarkSweepAlwaysCompactCount控制
* MarkSweepAlwaysCompactCount控制执行完全压缩的频率,默认值是4 \
*/ \
//total_invocations方法返回调用MarkSweep执行标记清理任务的次数
uint invocations = MarkSweep::total_invocations(); \
//invocations能否被MarkSweepAlwaysCompactCount整除
bool skip_dead = ((invocations % MarkSweepAlwaysCompactCount) != 0); \
\
size_t allowed_deadspace = 0; \
if (skip_dead) {
//计算允许的死的对象对应的空间,该方法默认返回0,只有TenuredSpace改写了该方法实现 \
const size_t ratio = allowed_dead_ratio(); \
allowed_deadspace = (capacity() * ratio / 100) / HeapWordSize; \
} \
\
//对象扫描的起始地址
HeapWord* q = bottom(); \
HeapWord* t = scan_limit(); \
//q的前一个字节属于最后一个活的对象 \
HeapWord* end_of_live= q; /* One byte beyond the last byte of the last \
live object. */
//first_dead表示第一个死的未被标记的对象 \
HeapWord* first_dead = end();/* The first dead object. */ \
//liveRange记录一段连续的活的对象的内存区域,liveRange本身保存在deadspace的起始地址处,所以不会覆盖掉活的对象的数据
LiveRange* liveRange = NULL; /* The current live range, recorded in the \
first header of preceding free area. */ \
_first_dead = first_dead; \
\
const intx interval = PrefetchScanIntervalInBytes; \
\
//block_is_obj方法要求参数q必须是一个内存块的起始地址,如果该内存块是一个对象则返回true
//oop(q)->mark()返回表示这个对象的对象头markOop,is_marked表示该对象被标记了, is_unlocked表示该对象没有持有锁,has_bias_pattern表示该对象持有偏向锁
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"); \
//如果q是一个对象,并且被GC标记了
if (block_is_obj(q) && oop(q)->is_gc_marked()) { \
/*将end后interval字节的数据预先获取放到高速缓存中 */ \
Prefetch::write(q, interval);
//block_size方法要求入参是一个起始内存块的地址,返回该内存块的大小 \
size_t size = block_size(q);
//计算移动的新地址,并写入到q的对象头中 \
compact_top = cp->space->forward(oop(q), size, cp, compact_top); \
q += size; \
end_of_live = q; \
} else { \
/* 跳过所有连续的死的需要被回收的对象 */ \
HeapWord* end = q; \
//不断往后循环,直到碰到一个被标记的活的对象,中间的非对象内存块和没有被标记的死的对象都被跳过了
do { \
//将end处的内存数据预先获取放到高速缓存 \
Prefetch::write(end, interval); \
//增加end
end += block_size(end); \
} while (end < t && (!block_is_obj(end) || !oop(end)->is_gc_marked()));\
\
/* \
* 如果允许存在这种deadspace(中间的非对象内存块和没有被标记的死的对象),则假装其是活的
* 这样就可以不用频繁的压缩了
*/ \
if (allowed_deadspace > 0 && q == compact_top) { \
//计算找到的这一段连续的deadspace的大小
size_t sz = pointer_delta(end, q); \
//如果allowed_deadspace大于sz则将这段deadspace用int数组填充,将其标记成一个活的对象,然后减少allowed_deadspace
//如果小于sz则返回false
if (insert_deadspace(allowed_deadspace, q, sz)) { \
//假装q是一个被标记的活的对象,计算其启动的目标地址
compact_top = cp->space->forward(oop(q), sz, cp, compact_top); \
q = end; \
end_of_live = end; \
continue; \
} \
} \
\
/*如果allowed_deadspace等于0或者小于sz,则需要回收这段deadspace */ \
\
/* for the previous LiveRange, record the end of the live objects. */ \
if (liveRange) { \
//liveRange不为空,则记录标记对象区域的终止地址
liveRange->set_end(q); \
} \
\
/* record the current LiveRange object. \
* liveRange->start() is overlaid on the mark word. \
*/ \
//创建一个新的LiveRange,q的位置位于deadspace,所以可以覆盖原来的数据
liveRange = (LiveRange*)q; \
//记录起始地址,因为end的下一个地址就是一个活的被标记对象
liveRange->set_start(end); \
liveRange->set_end(end); \
\
/* see if this is the first dead region. */ \
if (q < first_dead) { \
//更新第一个deadspace的地址
first_dead = q; \
} \
\
/* 往后遍历下一个被标记对象 */ \
q = end; \
} \
} \
\
//遍历完成
assert(q == t, "just checking"); \
if (liveRange != NULL) { \
//记录最后一个liveRange的结束地址
liveRange->set_end(q); \
} \
_end_of_live = end_of_live; \
//正常来说end_of_live会大于first_dead
if (end_of_live < first_dead) { \
//重置first_dead
first_dead = end_of_live; \
} \
_first_dead = first_dead; \
\
/* 保存*/ \
cp->space->set_compaction_top(compact_top); \
}
void set_compaction_top(HeapWord* value) {
assert(value == NULL || (value >= bottom() && value <= end()),
"should point inside space");
_compaction_top = value;
}
//部分地址连续的Space可能包含一种特殊的数据结构,如果内存分配跨过了某个边界就需要更新该数据结构
//initialize_threshold方法就是返回这个边界,默认实现是返回end,即内存分配永远不跨越边界
virtual HeapWord* initialize_threshold() { return end(); }
//在压缩的Space的存活对象区域中允许的死的对象的最大百分比
virtual size_t allowed_dead_ratio() const { return 0; };
size_t capacity() const { return byte_size(bottom(), end()); }
//判断对象q是否需要移动,如果需要则重置其对象头,将新地址写入到对象头中
//计算新地址时需要判断当前Space空间是否充足,如果不足则获取当前Generation的下一个支持压缩的Space,如果没有Space则从当前
//Generation的前一个Generation中获取Space,必须获取成功
HeapWord* CompactibleSpace::forward(oop q, size_t size,
CompactPoint* cp, HeapWord* compact_top) {
//校验q所属的Space和cp中的Space是一致的
assert(this == cp->space, "'this' should be current compaction space.");
//剩余的最大可用空间
size_t compaction_max_size = pointer_delta(end(), compact_top);
while (size > compaction_max_size) {
//当前Space可用空间不足了,获取下一个支持压缩的Space
cp->space->set_compaction_top(compact_top);
cp->space = cp->space->next_compaction_space();
if (cp->space == NULL) {
//如果没有下一个支持压缩的Space,获取前一个Generation,比如老年代空间不足了就获取年轻代
cp->gen = GenCollectedHeap::heap()->prev_gen(cp->gen);
assert(cp->gen != NULL, "compaction must succeed");
//修改cp的space
cp->space = cp->gen->first_compaction_space();
assert(cp->space != NULL, "generation must have a first compaction space");
}
//重置compact_top和compaction_max_size
compact_top = cp->space->bottom();
cp->space->set_compaction_top(compact_top);
cp->threshold = cp->space->initialize_threshold();
compaction_max_size = pointer_delta(cp->space->end(), compact_top);
}
// store the forwarding pointer into the mark word
if ((HeapWord*)q != compact_top) {
//如果q不等于compact_top,即需要移动q到compact_top
//forward_to方法用于重置q的对象头,将compact_top作为对象头的地址,并将新对象头打标
q->forward_to(oop(compact_top));
assert(q->is_gc_marked(), "encoding the pointer should preserve the mark");
} else {
//如果q等于compact_top,即不需要移动该对象
q->init_mark();
assert(q->forwardee() == NULL, "should be forwarded to NULL");
}
//增加compact_top
compact_top += size;
if (compact_top > cp->threshold)
cp->threshold =
cp->space->cross_threshold(compact_top - size, compact_top);
return compact_top;
}
inline void oopDesc::forward_to(oop p) {
//校验oop的地址是对齐的
assert(check_obj_alignment(p),
"forwarding to something not aligned");
//校验p在Java堆中
assert(Universe::heap()->is_in_reserved(p),
"forwarding to something not in heap");
//利用地址p生成一个新的对象头,将该对象头打上GC标记,最后修改当前对象的对象头
markOop m = markOopDesc::encode_pointer_as_mark(p);
//校验新对象头解析出来的地址等于p
assert(m->decode_pointer() == p, "encoding must be reversable");
set_mark(m);
}
inline static markOop encode_pointer_as_mark(void* p) { return markOop(p)->set_marked(); }
//当内存分配区域start和end跨过了threshold,则返回下一个threshold
virtual HeapWord* cross_threshold(HeapWord* start, HeapWord* the_end) {
return end();
}
bool CompactibleSpace::insert_deadspace(size_t& allowed_deadspace_words,
HeapWord* q, size_t deadlength) {
if (allowed_deadspace_words >= deadlength) {
//允许的deadspace的大小大于实际找到的deadspace的大小deadlength
//减少allowed_deadspace_words
allowed_deadspace_words -= deadlength;
//将q往后的deadlength大小的内存用int数组填充
CollectedHeap::fill_with_object(q, deadlength);
//将q对应的对象头打标
oop(q)->set_mark(oop(q)->mark()->set_marked());
assert((int) deadlength == oop(q)->size(), "bad filler object size");
// Recall that we required "q == compaction_top".
return true;
} else {
allowed_deadspace_words = 0;
return false;
}
}
2、CompactPoint / LiveRange / Prefetch
CompactPoint单纯就是一个数据结构而已,方便这三个属性在方法中传递,定义在space.hpp中,如下:
image.png
LiveRange继承自MemRegion,其定义在hotspot\src\share\vm\gc_implementation\shared\liveRange.hpp中,重点新增next和move_to方法,用来辅助GenMarkSweep移动对象,通过next方法所有的LiveRange被串联起来了,避免二次遍历Space,可以快速找到所有被标记的对象;move_to方法时拷贝整个连续的多个被GC标记对象区域。next方法之所以能够做到将所有的LiveRange串联起来,关键在于prepare_for_compaction方法,每次创建LiveRange时都是在一段连续的deadspace的起始地址创建,并且将该deadspace的终止地址作为LiveRange的start地址,将紧挨着该deadspace的下一段的连续被标记对象的内存区域的终止地址作为LiveRange的end地址,即end地址后又是一段连续的deadspace,又会在该地址上创建一个新的LiveRange。
image.pngPrefetch表示x86_64下的一个预提取功能,即预先将这部分内存加载到高速缓存中,方便CPU快速访问指定地址的数据,其定义在hotspot\src\share\vm\runtime\prefetch.hpp中,如下:
image.png其实现就是一条x86_64下的汇编指令,如下:
image.png
prefetcht0指令表示将指定地址的内存行加载到CPU的各级高速缓存中。
3、adjust_pointers
CompactibleSpace改写了父类的adjust_pointers的实现,增加了Space被压缩处理后的适配逻辑。其核心处理逻辑和父类是一样的,都是遍历所有被标记的活的对象的所有引用类型的属性,如果这些属性所引用的对象因为Space压缩需要被移动,则从这些对象的对象头获取新的地址,将这些引用类型的属性指向新的地址。其调用链如下:
image.png
其源码实现如下:
void CompactibleSpace::adjust_pointers() {
// Check first is there is any work to do.
//used方法返回已使用的空间,Space和CompactibleSpace都未提供默认实现
if (used() == 0) {
return; // Nothing to do.
}
SCAN_AND_ADJUST_POINTERS(adjust_obj_size);
}
#define adjust_obj_size(s) s
#define SCAN_AND_ADJUST_POINTERS(adjust_obj_size) { \
/* adjust all the interior pointers to point at the new locations of objects \
* 调整所有的内部指针指向对象的新地址
* Used by MarkSweep::mark_sweep_phase3() */ \
\
HeapWord* q = bottom(); \
/*prepare_for_compaction方法遍历Space时会更新_end_of_live和_first_dead,这两个属性是CompactibleSpace的属性 */
HeapWord* t = _end_of_live; \ \
assert(_first_dead <= _end_of_live, "Stands to reason, no?"); \
\
//_first_dead > q表示q所处的区域不是deadspace,但是q又没有被标记,说明q所处的区域在prepare_for_compaction
//处理时被当做成一个假的活的对象处理了,因此从q到_first_dead之间的区域不能用is_gc_marked来判断,因为该方法一定返回false
if (q < t && _first_dead > q && \
!oop(q)->is_gc_marked()) { \
HeapWord* end = _first_dead; \
\
while (q < end) { \
//校验q是一个对象的地址 \
assert(block_is_obj(q), \
"should be at block boundaries, and should be looking at objs"); \
\
/*遍历对象q中所有引用类型属性,如果所引用的对象发生移动了,则修改引用的对象地址为对象头中保存的新地址
adjust_pointers方法返回该对象的大小
*/ \
size_t size = oop(q)->adjust_pointers(); \
//adjust_obj_size是宏,就返回size
size = adjust_obj_size(size); \
//加上size,开始遍历下一个对象 \
q += size; \
} \
\
if (_first_dead == t) { \
//_first_dead等于t,说明t之前的内存区域都是做作为假的活的对象处理的,整个处理结束
q = t; \
} else { \ \
//如果不是一个存活的对象,则它的对象头应该指向下一个活的对象,通过prepare_for_compaction中生成的LiveRange处理的
q = (HeapWord*)oop(_first_dead)->mark()->decode_pointer(); \
} \
} \
\
const intx interval = PrefetchScanIntervalInBytes; \
\
debug_only(HeapWord* prev_q = NULL); \
while (q < t) { \
/* prefetch beyond q */ \
//预先读取地址q处的数据到高速缓存中
Prefetch::write(q, interval); \
if (oop(q)->is_gc_marked()) { \
/*如果q是被标记的活的对象,则遍历该对象的所有引用属性,如果所引用的对象发生移动了,则修改引用的对象地址为对象头中保存的新地址 */ \
size_t size = oop(q)->adjust_pointers(); \
size = adjust_obj_size(size); \
debug_only(prev_q = q); \
q += size; \
} else { \
/* 如果不是一个存活的对象,则它的对象头应该指向下一个活的对象 \
*/ \
debug_only(prev_q = q); \
q = (HeapWord*) oop(q)->mark()->decode_pointer(); \
assert(q > prev_q, "we should be moving forward through memory"); \
} \
} \
\
assert(q == t, "just checking"); \
}
inline int oopDesc::adjust_pointers() {
debug_only(int check_size = size());
int s = klass()->oop_adjust_pointers(this);
assert(s == check_size, "should be the same");
return s;
}
int InstanceKlass::oop_adjust_pointers(oop obj) {
//size_helper获取这个类即oop的内存大小
int size = size_helper();
//宏定义
InstanceKlass_OOP_MAP_ITERATE( \
obj, \
MarkSweep::adjust_pointer(p), \
assert_is_in)
return size;
}
template <class T> inline void MarkSweep::adjust_pointer(T* p) {
T heap_oop = oopDesc::load_heap_oop(p);
if (!oopDesc::is_null(heap_oop)) {
//oop非空,decode_heap_oop_not_null用于对压缩指针解码
oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);
//获取obj对象头保存的新地址,即该对象需要被移动的地址
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?
"should be forwarded");
if (new_obj != NULL) {
//校验new_obj在Java堆内
assert(Universe::heap()->is_in_reserved(new_obj),
"should be in object space");
//将p指向新的对象地址
oopDesc::encode_store_heap_oop_not_null(p, new_obj);
}
}
}
inline void oopDesc::encode_store_heap_oop_not_null(narrowOop* p, oop v) {
*p = encode_heap_oop_not_null(v);
}
inline void oopDesc::encode_store_heap_oop_not_null(oop* p, oop v) { *p = v; }
// The following macros call specialized macros, passing either oop or
// narrowOop as the specialization type. These test the UseCompressedOops
// flag.
#define InstanceKlass_OOP_MAP_ITERATE(obj, do_oop, assert_fn) \
{ \
/*计算内嵌在InstanceKlass后面的oopmap的起始地址*/ \
OopMapBlock* map = start_of_nonstatic_oop_maps(); \
OopMapBlock* const end_map = map + nonstatic_oop_map_count(); \
if (UseCompressedOops) { \
while (map < end_map) { \
//遍历每一个OopMapBlock
InstanceKlass_SPECIALIZED_OOP_ITERATE(narrowOop, \
//注意此处的起始地址是obj的,即OopMapBlock保存的是Java对象中所有引用类型属性在oop中存储的位置和个数
//所有的引用类型属性在oop中是连续保存的,对应的内存起始位置和个数就通过OopMapBlock保存
//所以OopMapBlock在Klass中通常只有一个
//所以这里实际遍历的是obj中所有的引用类型属性,如果所引用的对象发生了对象复制,则通过对象头获取新的地址,
//将属性指向新的地址
obj->obj_field_addr<narrowOop>(map->offset()), map->count(), \
do_oop, assert_fn) \
++map; \
} \
} else { \
while (map < end_map) { \
//遍历每一个OopMapBlock
InstanceKlass_SPECIALIZED_OOP_ITERATE(oop, \
obj->obj_field_addr<oop>(map->offset()), map->count(), \
do_oop, assert_fn) \
++map; \
} \
} \
}
//OopMapBlock是内嵌在InstanceKlass itable之后的一个数据结构
OopMapBlock* start_of_nonstatic_oop_maps() const {
return (OopMapBlock*)(start_of_itable() + align_object_offset(itable_length()));
}
//_nonstatic_oop_map_size是InstanceKlass的一个属性,记录OopMapBlock所占的字节数,单位是字宽,通常情况_nonstatic_oop_map_size的值是1
//OopMapBlock的只有两个int属性,其大小通常也是1个字宽
unsigned int nonstatic_oop_map_count() const {
return _nonstatic_oop_map_size / OopMapBlock::size_in_words();
}
//
// 遍历指定类型的oop,各参数如下:
// T - oop的类型,oop 或者 narrowOop
// start_p - 开始遍历的内存区域的起始地址
// count - 遍历的oop个数
// do_oop - 对单个oop执行的动作,期望是C函数,效率更高
// assert_fn - 用来校验的方法
#define InstanceKlass_SPECIALIZED_OOP_ITERATE( \
T, start_p, count, do_oop, \
assert_fn) \
{ \
//遍历内存的起始地址
T* p = (T*)(start_p); \
T* const end = p + (count); \
while (p < end) { \
(assert_fn)(p); \
do_oop; \
++p; \
} \
}
其中OopMapBlock的定义如下:
image.png
如其注释所说,该类用于描述某个Klass的实例即Java对象的引用类型的oop属性在哪儿保存的,offset是Java对象的oop属性在对象内部的偏移量,count属性表示Java对象中引用类型的oop属性有多少个,这些oop属性在Java对象中是连续保存的。
4、compact
compact方法用于执行对象复制,遍历Space中所有被标记的活的对象,从对象头中解析出对象复制的目的地址,然后根据对象大小执行对象复制,复制完成后将新对象的对象头恢复成初始状态,遍历结束最后清理重置Space。其调用链如下:
image.pngvoid CompactibleSpace::compact() {
SCAN_AND_COMPACT(obj_size);
}
#define SCAN_AND_COMPACT(obj_size) { \
/* 复制所有存活的对象到新的地址上 \
* Used by MarkSweep::mark_sweep_phase4() */ \
\
HeapWord* q = bottom(); \
HeapWord* const t = _end_of_live; \
debug_only(HeapWord* prev_q = NULL); \
\
//说明q到_first_dead之间都是假装是活的对象
if (q < t && _first_dead > q && \
!oop(q)->is_gc_marked()) { \
debug_only( \
/* we have a chunk of the space which hasn't moved and we've reinitialized \
* the mark word during the previous pass, so we can't use is_gc_marked for \
* the traversal. */ \
HeapWord* const end = _first_dead; \
\
//遍历q到_first_dead之间的对象,只是遍历未做实际处理
while (q < end) { \
size_t size = obj_size(q); \
assert(!oop(q)->is_gc_marked(), \
"should be unmarked (special dense prefix handling)"); \
debug_only(prev_q = q); \
q += size; \
} \
) /* debug_only */ \
\
if (_first_dead == t) { \
q = t; \
} else { \
/*获取下一个存活的对象的地址 */ \
q = (HeapWord*) oop(_first_dead)->mark()->decode_pointer(); \
} \
} \
\
const intx scan_interval = PrefetchScanIntervalInBytes; \
const intx copy_interval = PrefetchCopyIntervalInBytes; \
while (q < t) { \
if (!oop(q)->is_gc_marked()) { \
/* 如果不是存活对象则通过对象头找出下一个存活的对象 */ \
debug_only(prev_q = q); \
q = (HeapWord*) oop(q)->mark()->decode_pointer(); \
assert(q > prev_q, "we should be moving forward through memory"); \
} else { \
/* 提前读取内存地址为q的数据 */ \
Prefetch::read(q, scan_interval); \
\
/* size and destination */ \
//获取对象大小和对象拷贝的目的地址
size_t size = obj_size(q); \
HeapWord* compaction_top = (HeapWord*)oop(q)->forwardee(); \
\
/*预先加载地址为compaction_top的数据 */ \
Prefetch::write(compaction_top, copy_interval); \
\
/* copy object and reinit its mark */ \
assert(q != compaction_top, "everything in this pass should be moving"); \
//将对象从q复制到compaction_top
Copy::aligned_conjoint_words(q, compaction_top, size); \
//重新初始化对象头
oop(compaction_top)->init_mark(); \
//校验对象复制的结果是否正确
assert(oop(compaction_top)->klass() != NULL, "should have a class"); \
\
debug_only(prev_q = q); \
q += size; \
} \
} \
\
/* 判断Space是否被使用了*/ \
bool was_empty = used_region().is_empty(); \
/*对象复制结束后执行Space重置 */ \
reset_after_compaction(); \
if (used_region().is_empty()) { \
//rset后变成空的,但是之前不是空的,则需要清理之前的脏数据
if (!was_empty) clear(SpaceDecorator::Mangle); \
} else {
//填充未使用的区域 \
if (ZapUnusedHeapArea) mangle_unused_area(); \
} \
}
inline oop oopDesc::forwardee() const {
return (oop) mark()->decode_pointer();
}
inline void oopDesc::init_mark() { set_mark(markOopDesc::prototype_for_object(this)); }
5、如何获取下一个被标记对象的地址
在adjust_pointers和compact方法中都是使用q = (HeapWord)oop(_first_dead)->mark()->decode_pointer(); 和q = (HeapWord) oop(q)->mark()->decode_pointer(); 的方式获取下一个被标记对象的地址,第一个_first_dead和第二个oop的入参q都表示没有标记的死的对象的地址,为什么他们的对象头中保存了下一个被标记对象的地址?我们在prepare_for_compaction中也没有在没有标记的对象的对象头中写入下一个被标记对象的地址,这是为啥呢?
答案在LiveRange和oopDesc的内存结构。我们知道prepare_for_compaction方法在一段起始的deadspace处会创建一个新的LiveRange对象,然后保存下一个被标记对象的地址。那么上述代码是否是读取LiveRange对象中保存的下一个被标记对象的地址了?先看下两者的内存结构。LiveRange继承自MemRegion,没有添加新的属性,MemRegion的属性如下图:
image.png而oopDesc定义的属性如下图:
image.png这里的_mark实际也是一个指针,因为markOop实际是markOopDesc的别名,如下:
image.png再看下mark方法和decode_pointer方法的实现,如下:
markOop mark() const { return _mark; }
//decode_pointer实际就是还原_mark属性在构造时的oop
inline void* decode_pointer() { if (UseBiasedLocking && has_bias_pattern()) return NULL; return clear_lock_bits(); }
markOop clear_lock_bits() { return markOop(value() & ~lock_mask_in_place); }
参考对象复制时mark()->decode_pointer()方法的调用,该方法实际读取的就是本来的_mark属性,即地址q处的后8字节的数据。
因为C++对象在内存中其实就是若干个定义的属性连在一起的一个内存块,LiveRange和oopDesc的前8个字节都是表示一个指针地址,所以(HeapWord*) oop(q)->mark()->decode_pointer();表面看上去是读取的oopDesc的_mark属性,实际是读取之前的创建的LiveRange的start属性,即下一个存活的被标记对象的地址。
6、对象复制总结
参考上述方法的调用链可知,prepare_for_compaction,adjust_pointers和compact就是对象复制的核心实现了,对象复制的目的就是为了压缩Java堆,让Java堆中对象的分布更紧凑,减少因垃圾对象导致的内存碎片,从而留出足够大的内存空间分配大对象。其中prepare_for_compaction主要负责计算被标记对象需要被复制到的目的地址,并将目的地址保存到源对象的对象头中,注意计算移动的目的地址时是从当前Space的bottom属性开始的;adjust_pointers方法通过遍历所有被标记对象的引用类型属性,如果这些属性引用的对象需要对象复制则修改引用指向新的目的地址;compact负责根据源对象的对象头读取出复制的目的地址,然后将所有被标记的对象复制到目的地址上,注意复制时是从bottom到_end_of_live按照内存地址从低到高的顺序依次执行对象复制的,从而保证复制过去的对象不会覆盖掉未复制的对象的数据。
上述三个方法分别对应于GenMarkSweep的mark_sweep_phase2,mark_sweep_phase3和mark_sweep_phase4的底层实现,GenMarkSweep就是负责垃圾回收标记清除和对象复制的核心入口类,后续博客会详细介绍该类的具体实现。
网友评论