1. 简介
自从JDK10中的引入了JEP 304: Garbage Collector Interface 后,OpenJDK定义了一整套关于GC的虚方法,供具体的GC算法实现。极大了简化了开发难度和代码的可维护性。
JEP 304定义了CollectedHeap类,每个GC都需要实现。CollectedHeap类负责驱动HotSpot的GC,以及和其他模块的交互。GC应当实现如下功能:
- CollectedHeap的子类
- BarrierSet集合类的实现,提供在运行时各种屏障功能。
- CollectorPolicy类的实现
- GCInterpreterSupport的实现,提供GC在解释执行时各种屏障功能(使用汇编指令)
- GCC1Support的实现, 提供GC在C1编译代码中各种屏障功能
- GCC2Support的实现, 提供GC在C2编译代码中各种屏障功能
- 最终GC指定参数的初始化
- 一个MemoryService,提供内存池、内存管理等
通常的,对象分配的入口在InstanceKlass::allocate_instance,该方法调用heap->obj_allocate()进行分配。
instanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
int size = size_helper(); // Query before forming handle.
instanceOop i;
i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
// 对于实现了finalize方法的类的实例的特殊处理
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
2. CollectedHeap对象分配流程图
对象分配一般如下流程:
3. 源码分析
3.1 ZCollectedHeap
ZCollectedHeap重载了CollectedHeap的方法,其中包含了对象分配的相关方法。而核心逻辑在放在ZHeap中。ZCollectedHeap中主要的成员方法如下:
class ZCollectedHeap : public CollectedHeap {
friend class VMStructs;
private:
// 软引用清理策略
SoftRefPolicy _soft_ref_policy;
// 内存屏障,解释执行/C1/C2执行时对象访问的屏障
ZBarrierSet _barrier_set;
// 初始化逻辑
ZInitialize _initialize;
// 堆管理的核心逻辑,包括对象分配、转移、标记
ZHeap _heap;
// 垃圾回收线程,触发
ZDirector* _director;
// 垃圾回收线程,执行
ZDriver* _driver;
// 垃圾回收线程,统计
ZStat* _stat;
// 工作线程
ZRuntimeWorkers _runtime_workers;
}
3.2 ZHeap
ZHeap是ZGC内存管理的核心类。主要变量如下:
class ZHeap {
friend class VMStructs;
private:
static ZHeap* _heap;
// 工作线程
ZWorkers _workers;
// 对象分配器
ZObjectAllocator _object_allocator;
// 页面分配器
ZPageAllocator _page_allocator;
// 页表
ZPageTable _page_table;
// 转发表,用于对象迁移后的指针映射
ZForwardingTable _forwarding_table;
// 标记管理
ZMark _mark;
// 引用处理器
ZReferenceProcessor _reference_processor;
// 弱引用处理器
ZWeakRootsProcessor _weak_roots_processor;
// 转移管理器,用于对象迁移(类比G1的疏散)
ZRelocate _relocate;
// 转移集合
ZRelocationSet _relocation_set;
// 从元空间卸载类
ZUnload _unload;
ZServiceability _serviceability;
}
3.3 对象分配器
对象分配的主要逻辑在ZObjectAllocator。
3.3.1 对象分配器主要变量
ZObjectAllocator的主要变量如下:
class ZObjectAllocator {
private:
const bool _use_per_cpu_shared_small_pages;
// 分CPU记录使用内存size
ZPerCPU<size_t> _used;
// 分CPU记录undo内存size
ZPerCPU<size_t> _undone;
// 缓存行对齐的模板类
ZContended<ZPage*> _shared_medium_page;
// 按CPU从缓存分配对象
ZPerCPU<ZPage*> _shared_small_page;
}
3.3.2 分配方法
对象分配的核心方法是alloc_object
uintptr_t ZObjectAllocator::alloc_object(size_t size, ZAllocationFlags flags) {
if (size <= ZObjectSizeLimitSmall) {
// Small
return alloc_small_object(size, flags);
} else if (size <= ZObjectSizeLimitMedium) {
// Medium
return alloc_medium_object(size, flags);
} else {
// Large
return alloc_large_object(size, flags);
}
}
- 按对象的size,决定调用small page分配、medium page分配还是large page分配。
- 分配入参除了size外,还有个ZAllocationFlags。ZAllocationFlags是个8bit的配置参数。
large page分配方法如下:
uintptr_t ZObjectAllocator::alloc_large_object(size_t size, ZAllocationFlags flags) {
uintptr_t addr = 0;
// 对齐2MB
const size_t page_size = align_up(size, ZGranuleSize);
// 分配页面
ZPage* const page = alloc_page(ZPageTypeLarge, page_size, flags);
if (page != NULL) {
// 在页面中分配对象
addr = page->alloc_object(size);
}
return addr;
}
- small page分配和medium page分配都会调用到alloc_object_in_shared_page方法
- 小对象和中对象的分配略有不同,小对象是根据所在CPU从共享页面中分配对象。而中对象则是全部线程共享一个medium page。
// shared_page:页面地址
// page_type:page类型,small还是medium
// page_size: page size
// size: 对象size
// flags: 分配标识
uintptr_t ZObjectAllocator::alloc_object_in_shared_page(ZPage** shared_page,
uint8_t page_type,
size_t page_size,
size_t size,
ZAllocationFlags flags) {
uintptr_t addr = 0;
// 获取一个page
ZPage* page = Atomic::load_acquire(shared_page);
if (page != NULL) {
// 调用page的分配对象方法
addr = page->alloc_object_atomic(size);
}
if (addr == 0) {
// 如果刚才没有获取page成功,则分配一个new page
ZPage* const new_page = alloc_page(page_type, page_size, flags);
if (new_page != NULL) {
// 先分配对象,然后加载page到shared_page缓存
addr = new_page->alloc_object(size);
retry:
// 加载page到shared_page缓存
ZPage* const prev_page = Atomic::cmpxchg(shared_page, page, new_page);
if (prev_page != page) {
if (prev_page == NULL) {
// 如果prev_page已经淘汰,则goto到retry一直重试
page = prev_page;
goto retry;
}
// 其他线程加载了页面,则使用prev_page分配
const uintptr_t prev_addr = prev_page->alloc_object_atomic(size);
if (prev_addr == 0) {
// 如果分配失败,则goto到retry一直重试
page = prev_page;
goto retry;
}
addr = prev_addr;
undo_alloc_page(new_page);
}
}
}
return addr;
}
3.4 Page内的对象分配
page内的对象分配主要是两个方法alloc_object_atomic和alloc_object,其中alloc_object没有锁竞争,主要用于新page的第一次对象分配。
先看alloc_object_atomic
inline uintptr_t ZPage::alloc_object_atomic(size_t size) {
assert(is_allocating(), "Invalid state");
// 对象对齐,默认8字节对齐
const size_t aligned_size = align_up(size, object_alignment());
uintptr_t addr = top();
for (;;) {
const uintptr_t new_top = addr + aligned_size;
if (new_top > end()) {
// page没有申昱空间,则返回0
return 0;
}
// cas操作更新prev_top指针
const uintptr_t prev_top = Atomic::cmpxchg(&_top, addr, new_top);
if (prev_top == addr) {
// 调用ZAddress::good获取colored pointer
return ZAddress::good(addr);
}
// 无限重试
addr = prev_top;
}
}
在看看alloc_object
inline uintptr_t ZPage::alloc_object(size_t size) {
assert(is_allocating(), "Invalid state");
// 对象空间对齐,默认8字节
const size_t aligned_size = align_up(size, object_alignment());
const uintptr_t addr = top();
const uintptr_t new_top = addr + aligned_size;
if (new_top > end()) {
// 剩余空间不足,返回0
return 0;
}
_top = new_top;
// 调用ZAddress::good获取colored pointer
return ZAddress::good(addr);
}
3.5 Colored pointer的计算
可以看到上述两个方法在分配结束都调用了ZAddress::good返回colored pointer。看看ZAddress::good的实现。
inline uintptr_t ZAddress::offset(uintptr_t value) {
return value & ZAddressOffsetMask;
}
inline uintptr_t ZAddress::good(uintptr_t value) {
return offset(value) | ZAddressGoodMask;
}
void ZAddress::set_good_mask(uintptr_t mask) {
ZAddressGoodMask = mask;
ZAddressBadMask = ZAddressGoodMask ^ ZAddressMetadataMask;
ZAddressWeakBadMask = (ZAddressGoodMask | ZAddressMetadataRemapped | ZAddressMetadataFinalizable) ^ ZAddressMetadataMask;
}
- good方法其实挺简单,先取4位染色值,然后或操作实际地址,获取colored pointer。
- colored pointer将在load barrier中使用,后文将详细介绍load barrier机制。
4. 总结
本文介绍了ZGC中对象分配的核心代码和流程,下篇文章将继续介绍读屏障load barrier机制。