内存管理——记忆集和读写屏障
由于对堆使用了分代管理,所以在对新生代进行GC的时候需要将老年代对新生代中对象的引用也当做根来看待,所以在根标记的过程中也需要对老年代的引用进行标记,为了快速遍历老年代,所以需要使用记忆集来记录老年代的哪些部分可能含有对新生代的引用,同时引用可能被修改所以就需要有读写屏障来对引用关系的修改进行追踪,以达到更新记忆集的目的。
这里的记忆集指的是CardTableRS
,屏障指的是CardTableBarrierSet
。由于假设读者知道记忆集和读写屏障是干什么的,所以直接从使用他们开始。
putfield
首先来看,在解释执行的时候如何修改一个域,从putfield开始看,直到看到和读写屏障有关的地方我们停下来去看读写屏障。来看一下关于putfield的字节码实现,其中删除了一些逻辑,只保留了修改的字段为int的情况:
void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteControl rc) {
transition(vtos, vtos);
const Register cache = rcx; // 常量池缓存指针。
const Register index = rdx; // 在常量池缓存中索引。
const Register obj = rcx; // 要进行修改的目标对象,可能是instance对象可能是class对象。
const Register off = rbx; // 偏移。
const Register flags = rax;
const Register bc = LP64_ONLY(c_rarg3) NOT_LP64(rcx);
resolve_cache_and_index(byte_no, cache, index, sizeof(u2)); // 尝试对字段进行解析,获取其在常量池缓存的索引。
load_field_cp_cache_entry(obj, cache, index, off, flags, is_static); // 对缓存项进行加载,获取字段对应的偏移。
// field addresses
const Address field(obj, off, Address::times_1, 0*wordSize); // 构建地址。
// itos
{
__ pop(itos); // 将int加载到rax。
if (!is_static) pop_and_check_object(obj); // 获取obj,在load_field_cp_cache_entry并没有为putfield获取obj,只为putstatic获取了mirror。
__ access_store_at(T_INT, IN_HEAP, field, rax, noreg, noreg); // 访问,待会重点看这里面生成了什么。
if (!is_static && rc == may_rewrite) {
patch_bytecode(Bytecodes::_fast_iputfield, bc, rbx, true, byte_no);
}
__ jmp(Done); // 结束。
}
__ bind(Done);
}
关于常量池缓存和指令重写的内容在
void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, Address dst, Register src,
Register tmp1, Register tmp2) {
BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler();
decorators = AccessInternal::decorator_fixup(decorators);
bool as_raw = (decorators & AS_RAW) != 0;
if (as_raw) {
bs->BarrierSetAssembler::store_at(this, decorators, type, dst, src, tmp1, tmp2);
} else {
bs->store_at(this, decorators, type, dst, src, tmp1, tmp2);
}
}
由于并不是AS_RAW
的,所以只看第二个分支就行了,第一个分支中的bs->BarrierSetAssembler::store_at
,平时不是很常见,BarrierSetAssembler::store_at
是一个虚函数,我们平时都希望使用子类覆盖的实现,但是这里却强制使用了在BarrierSetAssembler
定义的实现。
我们进入到store_at的具体实现来看看,唯一一个覆盖其实就是ModRefBarrierSetAssembler::store_at
:
void ModRefBarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
Address dst, Register val, Register tmp1, Register tmp2) {
if (type == T_OBJECT || type == T_ARRAY) {
oop_store_at(masm, decorators, type, dst, val, tmp1, tmp2);
} else {
BarrierSetAssembler::store_at(masm, decorators, type, dst, val, tmp1, tmp2);
}
}
我们可以看到第二个分支还是使用了BarrierSetAssembler::store_at
,所以我们来看看里面生成了哪些代码,里面其实并没有使用记忆集,所以在使用as_raw
的时候会直接使用这个方法:
void BarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
Address dst, Register val, Register tmp1, Register tmp2) {
bool in_heap = (decorators & IN_HEAP) != 0;
bool in_native = (decorators & IN_NATIVE) != 0;
bool is_not_null = (decorators & IS_NOT_NULL) != 0;
bool atomic = (decorators & MO_RELAXED) != 0;
switch (type) {
case T_OBJECT:
case T_ARRAY: {
if (in_heap) {
if (val == noreg) { // 传入NULL。
assert(!is_not_null, "inconsistent access");
if (UseCompressedOops) {
__ movl(dst, (int32_t)NULL_WORD);
} else {
__ movslq(dst, (int32_t)NULL_WORD);
}
} else {
if (UseCompressedOops) {
assert(!dst.uses(val), "not enough registers");
if (is_not_null) {
__ encode_heap_oop_not_null(val); // 存储时修改为32位表示。
} else {
__ encode_heap_oop(val);
}
__ movl(dst, val);
} else {
__ movptr(dst, val);
}
}
} else {
assert(in_native, "why else?");
assert(val != noreg, "not supported");
__ movptr(dst, val);
}
break;
}
case T_BOOLEAN:
__ andl(val, 0x1); // boolean is true if LSB is 1
__ movb(dst, val);
break;
case T_BYTE:
__ movb(dst, val);
break;
case T_SHORT:
__ movw(dst, val);
break;
case T_CHAR:
__ movw(dst, val);
break;
case T_INT:
__ movl(dst, val);
break;
case T_LONG:
assert(val == noreg, "only tos");
__ movq(dst, rax);
break;
case T_FLOAT:
assert(val == noreg, "only tos");
__ store_float(dst);