概览

ZGC 在 JDK11 中作为试验性功用引进后,现已经过了 5 个版别的演进,现在较之前版别有了较大的改变。本文将剖析 ZGC 的规划思维和原理。

ZGC 首要规划理念如下:

  • ZGC 为了支撑 TB 级内存,选用了基于 Page 的分页办理(相似于 G1 的 Region)。
  • 一起,为了加速内存拜访速度,快速的进行并发符号和 relocate,ZGC 新引进了 Color Pointers;Color Pointers 与 Shenandoah GC 运用的 Brooks Pointers 机制不同,依靠内核供给的多视图映射,因而仅能支撑部分操作体系的 64 位版别,适用性不如 Shenandoah GC,一起也无法支撑指针紧缩 CompressedOops。
  • 另外,为了高效内存办理,规划了两级内存办理体系。

内存办理

指针结构

zGlobals_x86.cpp

//AddressSpace&PointerLayout3
//--------------------------------
//
//+--------------------------------+0x00007FFFFFFFFFFF(127TB)
//..
//..
//..
//+--------------------------------+0x0000500000000000(80TB)
//|RemappedView|
//+--------------------------------+0x0000400000000000(64TB)
//..
//+--------------------------------+0x0000300000000000(48TB)
//|Marked1View|
//+--------------------------------+0x0000200000000000(32TB)
//|Marked0View|
//+--------------------------------+0x0000100000000000(16TB)
//..
//+--------------------------------+0x0000000000000000
//
//64444
//387430
//+------------------+----+-------------------------------------------------+
//|0000000000000000|1111|11111111111111111111111111111111111111111111|
//+------------------+----+-------------------------------------------------+
//|||
//||*43-0ObjectOffset(44-bits,16TBaddressspace)
//||
//|*47-44MetadataBits(4-bits)0001=Marked0(Addressview16-32TB)
//|0010=Marked1(Addressview32-48TB)
//|0100=Remapped(Addressview64-80TB)
//|1000=Finalizable(AddressviewN/A)
//|
//*63-48Fixed(16-bits,alwayszero)
//
  • ZGC 指针布局有三种方式,别离用于支撑 4TB、8TB、16TB 的堆空间,以上代码用于为 layout 3 支撑 16TB 的布局;
  • 43-0 bit 目标地址;
  • 47-44 目标视图,分为三种目标视图:
    • Marked0、Marked1
    • Remapped
  • x86 和 aarch64 架构下最多仅支撑 48 位指针,首要是因为硬件约束。一般为了节约成本,64 位处理器地址线一般仅 40-50 条,因而寻址规模远不及 64 位的理论值。

多视图

ZGC 将同一段物理内存映射到 3 个不同的虚拟内存视图,别离为 Marked0、Marked1、Remapped,这即是 ZGC 中的 Color Pointers,通过 Color Pointers 区别不同的 GC 阶段。

映射

ZGC 的多视图映射依靠于内核供给的 mmap 办法,具体代码如下

zPhysicalMemory.hpp, zPhysicalMemory.cpp, zPhysicalMemoryBacking_linux.cpp

//物理内存办理类
classZPhysicalMemory{
private:
ZArray<ZPhysicalMemorySegment>_segments;
voidinsert_segment(intindex,uintptr_tstart,size_tsize,boolcommitted);
voidreplace_segment(intindex,uintptr_tstart,size_tsize,boolcommitted);
voidremove_segment(intindex);
public:
ZPhysicalMemory();
ZPhysicalMemory(constZPhysicalMemorySegment&segment);
ZPhysicalMemory(constZPhysicalMemory&pmem);
constZPhysicalMemory&operator=(constZPhysicalMemory&pmem);
boolis_null()const;
size_tsize()const;
intnsegments()const;
constZPhysicalMemorySegment&segment(intindex)const;
voidadd_segments(constZPhysicalMemory&pmem);
voidremove_segments();
voidadd_segment(constZPhysicalMemorySegment&segment);
boolcommit_segment(intindex,size_tsize);
booluncommit_segment(intindex,size_tsize);
ZPhysicalMemorysplit(size_tsize);
ZPhysicalMemorysplit_committed();
};
//将三个虚拟内存视图映射到同一物理内存
//在JDK14中添加了关于ZVerifyViewsJVM参数的支撑(https://bugs.openjdk.java.net/browse/JDK-8232604)
voidZPhysicalMemoryManager::map(uintptr_toffset,constZPhysicalMemory&pmem)const{
constsize_tsize=pmem.size();
if(ZVerifyViews){
//Mapgoodview
map_view(ZAddress::good(offset),pmem);
}else{
//Mapallviews
map_view(ZAddress::marked0(offset),pmem);
map_view(ZAddress::marked1(offset),pmem);
map_view(ZAddress::remapped(offset),pmem);
}
nmt_commit(offset,size);
}
voidZPhysicalMemoryManager::map_view(uintptr_taddr,constZPhysicalMemory&pmem)const{
size_tsize=0;
//逐一映射物理内存
//ZGC中运用segment办理物理内存,后续文章将具体介绍
for(inti=0;i<pmem.nsegments();i++){
constZPhysicalMemorySegment&segment=pmem.segment(i);
_backing.map(addr+size,segment.size(),segment.start());
size+=segment.size();
}
//SetupNUMAinterleavingforlargepages
if(ZNUMA::is_enabled()&&ZLargePages::is_explicit()){
//Togetgranule-levelNUMAinterleavingwhenusinglargepages,
//wesimplyletthekernelinterleavethememoryforusatpage
//faulttime.
os::numa_make_global((char*)addr,size);
}
}
//终究关于map的调用
//关于linux体系,调用mmap进行映射
voidZPhysicalMemoryBacking::map(uintptr_taddr,size_tsize,uintptr_toffset)const{
//可读、可写、批改同享
//假如参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做批改。
constvoid*constres=mmap((void*)addr,size,PROT_READ|PROT_WRITE,MAP_FIXED|MAP_SHARED,_fd,offset);
if(res==MAP_FAILED){
ZErrnoerr;
fatal("Failedtomapmemory(%s)",err.to_string());
}
}
  • ZPhysicalMemory 是 ZGC 关于物理内存办理的笼统,收敛 ZGC 关于物理内存的拜访。
  • ZPhysicalMemory 底层根据宿主操作体系调用不同的 ZPhysicalMemoryBacking 完成,进行多视图映射。

物理内存办理

ZGC 关于物理内存的办理首要在 ZPhysicalMemory 类中,此处需求注意,ZGC 上下文中的物理内存,不是真正的物理内存,而是操作体系虚拟内存。

OpenJDK16 ZGC 源码分析

ZGC 中办理物理内存的根本单位是 segment。segment 默许与 small page size 相同,都是 2MB。引进 segment 是为了避免频频的请求和开释内存的体系调用,一次请求 2MB,当 segment 空闲时,将参加空闲列表,等候之后重复运用。

zGlobals_x86.hpp

//默许pagesize偏移量
constsize_tZPlatformGranuleSizeShift=21;//2MB

ZPhysicalMemorySegment 是 ZGC 关于物理内存 segment 的笼统,界说如下:

zPhysicalMemory.cpp

private:
  // 开端偏移量
  uintptr_t _start;
  // 开端偏移量+size
  uintptr_t _end;
  bool      _committed;
public:
  ZPhysicalMemorySegment();
  ZPhysicalMemorySegment(uintptr_t start, size_t size, bool committed);
  uintptr_t start() const;
  uintptr_t end() const;
  size_t size() const;
  bool is_committed() const;
  void set_committed(bool committed);
};

页面办理

Page 介绍

ZGC 中内存办理的根本单元是 Page(相似于 G1 中的 region),ZGC 有 3 种不同的页面类型:小型(2MB),中型(32MB)和大型(2MB 的倍数)。

zGlobals_x86.hpp

constsize_tZPlatformGranuleSizeShift=21;//2MB

zGlobals.hpp

//Pagetypes
constuint8_tZPageTypeSmall=0;
constuint8_tZPageTypeMedium=1;
constuint8_tZPageTypeLarge=2;
//Pagesizeshifts
constsize_tZPageSizeSmallShift=ZGranuleSizeShift;
externsize_tZPageSizeMediumShift;
//Pagesizes
//smallpage2MB
constsize_tZPageSizeSmall=(size_t)1<<ZPageSizeSmallShift;
externsize_tZPageSizeMedium;
//目标size约束,smallpage不超过2MB/8,256KB
constsize_tZObjectSizeLimitSmall=ZPageSizeSmall/8;//12.5%maxwaste
externsize_tZObjectSizeLimitMedium;

medium 页 size 的计算办法如下:

zHeuristics.cpp

voidZHeuristics::set_medium_page_size(){
//SetZPageSizeMediumsothatamediumpageoccupiesatmost3.125%ofthe
//maxheapsize.ZPageSizeMediumisinitiallysetto0,whichmeansmedium
//pagesareeffectivelydisabled.ItisadjustedonlyifZPageSizeMedium
//becomeslargerthanZPageSizeSmall.
constsize_tmin=ZGranuleSize;
constsize_tmax=ZGranuleSize*16;
constsize_tunclamped=MaxHeapSize*0.03125;
constsize_tclamped=clamp(unclamped,min,max);
constsize_tsize=round_down_power_of_2(clamped);
if(size>ZPageSizeSmall){
//Enablemediumpages
ZPageSizeMedium=size;
ZPageSizeMediumShift=log2_intptr(ZPageSizeMedium);
ZObjectSizeLimitMedium=ZPageSizeMedium/8;
ZObjectAlignmentMediumShift=(int)ZPageSizeMediumShift-13;
ZObjectAlignmentMedium=1<<ZObjectAlignmentMediumShift;
}
}
  • 取堆最大容量(Xmx)的 0.03125 unclamped;
  • 假如 unclamped 在 2MB 到 32MB 之间,clamped 赋值 unclamped;假如 unclamped 小于 2MB,则 clamped 赋值 2MB;假如 unclamped 大于 32MB,则 clamped 赋值 32MB;
  • 向下取 clamped 最接近的 2 的幂数,即为 medium 页 size;
  • 考虑到现在的硬件环境,一般的 medium 页 size 为 32MB;
  • ZObjectSizeLimitMedium 为 ZPageSizeMedium / 8,则一般情况下,medium 页的目标 size 约束为 4MB。超过 4MB 的目标需求放入 large 页。

关于 large page 的处理如下:

zObjectAllocator.cpp

uintptr_tZObjectAllocator::alloc_large_object(size_tsize,ZAllocationFlagsflags){
uintptr_taddr=0;
//Allocatenewlargepage
constsize_tpage_size=align_up(size,ZGranuleSize);
ZPage*constpage=alloc_page(ZPageTypeLarge,page_size,flags);
if(page!=NULL){
//Allocatetheobject
addr=page->alloc_object(size);
}
returnaddr;
}
  • 分配大目标时,触发分配 large page;
  • 对齐大目标 size 到 2MB 的倍数后分配 large page。

zObjectAllocator.cpp

uintptr_tZObjectAllocator::alloc_object(size_tsize,ZAllocationFlagsflags){
if(size<=ZObjectSizeLimitSmall){
//Small
returnalloc_small_object(size,flags);
}elseif(size<=ZObjectSizeLimitMedium){
//Medium
returnalloc_medium_object(size,flags);
}else{
//Large
returnalloc_large_object(size,flags);
}
}
  • 当目标 size 大于 medium 页目标 size 约束时,触发大目标分配;
  • 因而,large 页的实践 size 很可能小于 medium 页 size。

Page 的分配

Page 分配的进口在 ZHeap 的 alloc_page 办法:

zHeap.cpp

ZPage*ZObjectAllocator::alloc_page(uint8_ttype,size_tsize,ZAllocationFlagsflags){
//调用了page分配器的alloc_page函数
ZPage*constpage=ZHeap::heap()->alloc_page(type,size,flags);
if(page!=NULL){
//添加运用内存数
Atomic::add(_used.addr(),size);
}
returnpage;
}

zPageAllocator.cpp

ZPage*ZPageAllocator::alloc_page(uint8_ttype,size_tsize,ZAllocationFlagsflags){
EventZPageAllocationevent;
retry:
ZPageAllocationallocation(type,size,flags);
//从pagecache分配page
//假如分配成功,调用alloc_page_finalize完结分配
//分配进程中,假如是堵塞形式,有可能在安全点被堵塞
if(!alloc_page_or_stall(&allocation)){
//Outofmemory
returnNULL;
}
//假如从pagecache分配失利,则从物理内存请求页
//提交page
ZPage*constpage=alloc_page_finalize(&allocation);
if(page==NULL){
//假如commit或许map失利,则goto到retry,重新分配
alloc_page_failed(&allocation);
gotoretry;
}
//...
//...
//...
returnpage;
}
boolZPageAllocator::alloc_page_or_stall(ZPageAllocation*allocation){
{
//分配page需求上锁,因为只有一个堆
ZLocker<ZLock>locker(&_lock);
//分配成功,回来true
if(alloc_page_common(allocation)){
returntrue;
}
//假如是非堵塞形式,回来false
if(allocation->flags().non_blocking()){
returnfalse;
}
//分配请求入队,等候GC完结
_stalled.insert_last(allocation);
}
returnalloc_page_stall(allocation);
}
//堵塞分配,等候GC
boolZPageAllocator::alloc_page_stall(ZPageAllocation*allocation){
ZStatTimertimer(ZCriticalPhaseAllocationStall);
EventZAllocationStallevent;
ZPageAllocationStallresult;
//查看虚拟机是否现已完结初始化
check_out_of_memory_during_initialization();
do{
//发动异步GC
ZCollectedHeap::heap()->collect(GCCause::_z_allocation_stall);
//挂起,等候GC结果
result=allocation->wait();
}while(result==ZPageAllocationStallStartGC);
//...
//...
//...
return(result==ZPageAllocationStallSuccess);
}
  • 堵塞分配与非堵塞分配,由体系参数 ZStallOnOutOfMemory 操控,默许堵塞分配。堵塞分配时,假如分配失利,则触发 GC,等候 GC 结束后再次分配,直到分配成功。

目标分配

自从 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()进行分配。

instanceOopInstanceKlass::allocate_instance(TRAPS){
boolhas_finalizer_flag=has_finalizer();//QuerybeforepossibleGC
intsize=size_helper();//Querybeforeforminghandle.
instanceOopi;
i=(instanceOop)Universe::heap()->obj_allocate(this,size,CHECK_NULL);
if(has_finalizer_flag&&!RegisterFinalizersAtInit){
//关于完成了finalize办法的类的实例的特别处理
i=register_finalizer(i,CHECK_NULL);
}
returni;
}

CollectedHeap 目标分配流程图

目标分配一般遵从如下流程:

OpenJDK16 ZGC 源码分析

源码剖析

ZCollectedHeap

ZCollectedHeap 重载了 CollectedHeap 的办法,其间包含了目标分配的相关办法。而中心逻辑在放在 ZHeap 中。ZCollectedHeap 中首要的成员办法如下:

classZCollectedHeap:publicCollectedHeap{
friendclassVMStructs;
private:
//软引证收拾战略
SoftRefPolicy_soft_ref_policy;
//内存屏障,解说执行/C1/C2执行时目标拜访的屏障
ZBarrierSet_barrier_set;
//初始化逻辑
ZInitialize_initialize;
//堆办理的中心逻辑,包含目标分配、搬运、符号
ZHeap_heap;
//废物收回线程,触发
ZDirector*_director;
//废物收回线程,执行
ZDriver*_driver;
//废物收回线程,统计
ZStat*_stat;
//作业线程
ZRuntimeWorkers_runtime_workers;
}

ZHeap

ZHeap 是 ZGC 内存办理的中心类。首要变量如下:

classZHeap{
friendclassVMStructs;
private:
staticZHeap*_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;
}

目标分配器

目标分配的首要逻辑在 ZObjectAllocator。

目标分配器首要变量

ZObjectAllocator 的首要变量如下:

classZObjectAllocator{
private:
constbool_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;
}
分配办法

目标分配的中心办法是 alloc_object

uintptr_tZObjectAllocator::alloc_object(size_tsize,ZAllocationFlagsflags){
if(size<=ZObjectSizeLimitSmall){
//Small
returnalloc_small_object(size,flags);
}elseif(size<=ZObjectSizeLimitMedium){
//Medium
returnalloc_medium_object(size,flags);
}else{
//Large
returnalloc_large_object(size,flags);
}
}
  • 按目标的 size,决议调用 small page 分配、medium page 分配仍是 large page 分配。
  • 分配入参除了 size 外,还有个 ZAllocationFlags。ZAllocationFlags 是个 8bit 的装备参数。

large page 分配办法如下:

uintptr_tZObjectAllocator::alloc_large_object(size_tsize,ZAllocationFlagsflags){
uintptr_taddr=0;
//对齐2MB
constsize_tpage_size=align_up(size,ZGranuleSize);
//分配页面
ZPage*constpage=alloc_page(ZPageTypeLarge,page_size,flags);
if(page!=NULL){
//在页面平分配目标
addr=page->alloc_object(size);
}
returnaddr;
}
  • small page 分配和 medium page 分配都会调用到 alloc_object_in_shared_page 办法;
  • 小目标和中目标的分配略有不同,小目标是根据地点 CPU 从同享页面平分配目标。而中目标则是悉数线程同享一个 medium page。
// shared_page:页面地址
// page_type:page类型,small仍是medium
//page_size:pagesize
//size:目标size
//flags:分配标识
uintptr_tZObjectAllocator::alloc_object_in_shared_page(ZPage**shared_page,
uint8_tpage_type,
size_tpage_size,
size_tsize,
ZAllocationFlagsflags){
uintptr_taddr=0;
//获取一个page
ZPage*page=Atomic::load_acquire(shared_page);
if(page!=NULL){
//调用page的分配目标办法
addr=page->alloc_object_atomic(size);
}
if(addr==0){
//假如刚才没有获取page成功,则分配一个newpage
ZPage*constnew_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*constprev_page=Atomic::cmpxchg(shared_page,page,new_page);
if(prev_page!=page){
if(prev_page==NULL){
//假如prev_page现已筛选,则goto到retry一直重试
page=prev_page;
gotoretry;
}
//其他线程加载了页面,则运用prev_page分配
constuintptr_tprev_addr=prev_page->alloc_object_atomic(size);
if(prev_addr==0){
//假如分配失利,则goto到retry一直重试
page=prev_page;
gotoretry;
}
addr=prev_addr;
undo_alloc_page(new_page);
}
}
}
returnaddr;
}

Page 内的目标分配

page 内的目标分配首要是两个办法 alloc_object_atomic 和 alloc_object,其间 alloc_object 没有锁竞赛,首要用于新 page 的第一次目标分配。

先看 alloc_object_atomic

inlineuintptr_tZPage::alloc_object_atomic(size_tsize){
assert(is_allocating(),"Invalidstate");
//目标对齐,默许8字节对齐
constsize_taligned_size=align_up(size,object_alignment());
uintptr_taddr=top();
for(;;){
constuintptr_tnew_top=addr+aligned_size;
if(new_top>end()){
//page没有申昱空间,则回来0
return0;
}
//cas操作更新prev_top指针
constuintptr_tprev_top=Atomic::cmpxchg(&_top,addr,new_top);
if(prev_top==addr){
//调用ZAddress::good获取coloredpointer
returnZAddress::good(addr);
}
//无限重试
addr=prev_top;
}
}

再看看 alloc_object

inlineuintptr_tZPage::alloc_object(size_tsize){
assert(is_allocating(),"Invalidstate");
//目标空间对齐,默许8字节
constsize_taligned_size=align_up(size,object_alignment());
constuintptr_taddr=top();
constuintptr_tnew_top=addr+aligned_size;
if(new_top>end()){
//剩下空间不足,回来0
return0;
}
_top=new_top;
//调用ZAddress::good获取coloredpointer
returnZAddress::good(addr);
}

Colored pointer 的计算

能够看到上述两个办法在分配结束都调用了 ZAddress::good 回来 colored pointer。看看 ZAddress::good 的完成。

inlineuintptr_tZAddress::offset(uintptr_tvalue){
returnvalue&ZAddressOffsetMask;
}
inlineuintptr_tZAddress::good(uintptr_tvalue){
returnoffset(value)|ZAddressGoodMask;
}
voidZAddress::set_good_mask(uintptr_tmask){
ZAddressGoodMask=mask;
ZAddressBadMask=ZAddressGoodMask^ZAddressMetadataMask;
ZAddressWeakBadMask=(ZAddressGoodMask|ZAddressMetadataRemapped|ZAddressMetadataFinalizable)^ZAddressMetadataMask;
}
  • good 办法其实挺简略,先取 4 位染色值,然后或操作实践地址,获取 colored pointer。
  • colored pointer 将在 load barrier 中运用,后文将具体介绍 load barrier 机制。

读屏障

关于并发 GC 来说,最复杂的作业在于 GC worker 在符号-收拾,而 Java 线程(Mutator)一起还在不断的创立新目标、批改字段,不停的更新目标引证联系。因而并发 GC 一般选用两种战略 Incremental Update(增量更新、CMS) 和 SATB(snapshot at beginning、G1) ,两种战略网上介绍文章很多,此处不再赘述。

SATB 重点重视引证联系的删除,能够参考我之前的博客JVM G1 源码剖析(四)- Dirty Card Queue Set,而 Incremental Update 重点重视引证联系的添加。

而 ZGC 并没有采纳相似方式,而是凭借读屏障、colored pointer 来完成并发符号-收拾。

原理

什么是 Load Barrier

  • 一小段在最佳位置由 JIT 注入的代码
    • 从堆中加载一个目标引证时
  • 查看这个引证是否是 bad color
    • 假如是,则自愈

Load Barrier 的触发

从堆中加载目标引证时触发 load barrier。

//从堆中加载一个目标引证,需求loadbarrier
Stringn=person.name;
//不需求loadbarrier,不是从堆中加载
Stringp=n;
//不需求loadbarrier,不是从堆中加载
n.isEmpty();
//不需求loadbarrier,不是引证类型
intage=person.age;

当引证类型 n 被赋值批改后,在下一次运用 n 前,会测验 n 的染色指针是否为 good。此刻测验为 bad color 可知 n 的引证地址进行过批改,需求自愈。

触发 load barrier 的伪代码如下:

//从堆中加载一个目标引证,需求loadbarrier
Stringn=person.name;
if(n&bad_bit_mask){
slow_path(register_for(n),address_of)
}

对应的汇编代码:

//Stringn=person.name;
mov0x10(%rax),%rbx
//是否badcolor
test%rbx,(0x16)%r15
//如是,进入slowpath
jnzslow_path

源码剖析

掩码

zGlobals.hpp

//
//Good/Badmaskstates
//--------------------
//
//GoodMaskBadMaskWeakGoodMaskWeakBadMask
//--------------------------------------------------------------
//Marked0001110101010
//Marked1010101110001
//Remapped100011100011
//
//Good/badmasks
externuintptr_tZAddressGoodMask;
externuintptr_tZAddressBadMask;
externuintptr_tZAddressWeakBadMask;

zAddress.inline.hpp

inlineboolZAddress::is_null(uintptr_tvalue){
returnvalue==0;
}
inlineboolZAddress::is_bad(uintptr_tvalue){
returnvalue&ZAddressBadMask;
}
inlineboolZAddress::is_good(uintptr_tvalue){
return!is_bad(value)&&!is_null(value);
}

从以上两段代码能够很明晰看出,colored pointer 的状况是 Good/WeakGood/Bad/WeakBad 由 GoodMask 及 BadMask 来测定。

一起,GoodMask、BadMask 由 GC 所在的阶段决议。

voidZAddress::set_good_mask(uintptr_tmask){
ZAddressGoodMask=mask;
ZAddressBadMask=ZAddressGoodMask^ZAddressMetadataMask;
ZAddressWeakBadMask=(ZAddressGoodMask|ZAddressMetadataRemapped|ZAddressMetadataFinalizable)^ZAddressMetadataMask;
}
voidZAddress::initialize(){
ZAddressOffsetBits=ZPlatformAddressOffsetBits();
ZAddressOffsetMask=(((uintptr_t)1<<ZAddressOffsetBits)-1)<<ZAddressOffsetShift;
ZAddressOffsetMax=(uintptr_t)1<<ZAddressOffsetBits;
ZAddressMetadataShift=ZPlatformAddressMetadataShift();
ZAddressMetadataMask=(((uintptr_t)1<<ZAddressMetadataBits)-1)<<ZAddressMetadataShift;
ZAddressMetadataMarked0=(uintptr_t)1<<(ZAddressMetadataShift+0);
ZAddressMetadataMarked1=(uintptr_t)1<<(ZAddressMetadataShift+1);
ZAddressMetadataRemapped=(uintptr_t)1<<(ZAddressMetadataShift+2);
ZAddressMetadataFinalizable=(uintptr_t)1<<(ZAddressMetadataShift+3);
ZAddressMetadataMarked=ZAddressMetadataMarked0;
set_good_mask(ZAddressMetadataRemapped);
}
voidZAddress::flip_to_marked(){
ZAddressMetadataMarked^=(ZAddressMetadataMarked0|ZAddressMetadataMarked1);
set_good_mask(ZAddressMetadataMarked);
}
voidZAddress::flip_to_remapped(){
set_good_mask(ZAddressMetadataRemapped);
}

比方,ZGC 初始化后,地址视图为 Remapped,GoodMask 是 100,BadMask 是 011。进入符号阶段后,地址视图切换为 M0,GoodMask 和 BadMask 变更为 001 和 110。

屏障的进入条件

accessDecorators.cpp

//===AccessLocation===
//对堆的拜访
constDecoratorSetIN_HEAP=UCONST64(1)<<18;
//对堆外的拜访
constDecoratorSetIN_NATIVE=UCONST64(1)<<19;
constDecoratorSetIN_DECORATOR_MASK=IN_HEAP|IN_NATIVE;

zBarrierSet.cpp

boolZBarrierSet::barrier_needed(DecoratorSetdecorators,BasicTypetype){
assert((decorators&AS_RAW)==0,"Unexpecteddecorator");
//assert((decorators&ON_UNKNOWN_OOP_REF)==0,"Unexpecteddecorator");
//是否引证类型
if(is_reference_type(type)){
//是否从堆中或许堆外加载一个目标引证
assert((decorators&(IN_HEAP|IN_NATIVE))!=0,"Whereisreference?");
//BarrierneededevenwhenIN_NATIVE,toallowconcurrentscanning.
returntrue;
}
//Barriernotneeded
returnfalse;
}

屏障

load barrier 的进口代码在 zBarrier.inline.hpp

//模板函数
template<ZBarrierFastPathfast_path,ZBarrierSlowPathslow_path>
inlineoopZBarrier::barrier(volatileoop*p,oopo){
constuintptr_taddr=ZOop::to_address(o);
//假如是good指针,只需做一次类型转化
if(fast_path(addr)){
returnZOop::from_address(addr);
}
//否则,进入slowpath
constuintptr_tgood_addr=slow_path(addr);
//指针自愈
if(p!=NULL){
self_heal<fast_path>(p,addr,good_addr);
}
//类型转化
returnZOop::from_address(good_addr);
}
  • barrier 接收两个模板函数指针,根据输入函数的执行结果决议走 fast path 仍是 slow path;
  • fast path 仅需一次类型转化;
  • slow path 执行后,还需求进行指针自愈,最后回来前做类型转化。

fast path

fast path 根据执行场景和 colored pointer 不同有不少挑选,运用比较多的如下:zBarrier.inline.hpp

//又调回到ZAddress的inline函数了,都是一堆用coloredpointer&掩码的操作
inlineboolZBarrier::is_good_or_null_fast_path(uintptr_taddr){
returnZAddress::is_good_or_null(addr);
}
inlineboolZBarrier::is_weak_good_or_null_fast_path(uintptr_taddr){
returnZAddress::is_weak_good_or_null(addr);
}
inlineboolZBarrier::is_marked_or_null_fast_path(uintptr_taddr){
returnZAddress::is_marked_or_null(addr);
}

slow path

相同的 slow path 根据场景不同,也有好几个挑选,可是运用较多的就是 load_barrier_on_oop_slow_path zBarrier.cpp

uintptr_tZBarrier::load_barrier_on_oop_slow_path(uintptr_taddr){
//搬迁仍是符号
returnrelocate_or_mark(addr);
}
//搬迁
uintptr_tZBarrier::relocate(uintptr_taddr){
assert(!ZAddress::is_good(addr),"Shouldnotbegood");
assert(!ZAddress::is_weak_good(addr),"Shouldnotbeweakgood");
//调用heap的relocate_object
returnZHeap::heap()->relocate_object(addr);
}
搬迁目标

zHeap.inline.cpp zRelocate.cpp

//搬迁目标
inlineuintptr_tZHeap::relocate_object(uintptr_taddr){
assert(ZGlobalPhase==ZPhaseRelocate,"Relocatenotallowed");
//从forwardingtable拿到地址映射联系
// forwarding table会在后文介绍GC的执行进程时具体介绍。先简略了解成一个旧地址到新地址的映射好了。
ZForwarding*constforwarding=_forwarding_table.get(addr);
if(forwarding==NULL){
//不在forwardingtable内,那就是个goodaddress
returnZAddress::good(addr);
}
//搬迁目标
return_relocate.relocate_object(forwarding,ZAddress::good(addr));
}
//实践的搬迁办法
uintptr_tZRelocate::relocate_object(ZForwarding*forwarding,uintptr_tfrom_addr)const{
ZForwardingCursorcursor;
//在forwardingtable找到新地址
//假如新地址非0,则表明目标现已分散到新page了,直接回来新地址
//假如新地址为0,则先搬迁目标
uintptr_tto_addr=forwarding_find(forwarding,from_addr,&cursor);
if(to_addr!=0){
//Alreadyrelocated
returnto_addr;
}
//搬迁目标
if(forwarding->retain_page()){
to_addr=relocate_object_inner(forwarding,from_addr,&cursor);
forwarding->release_page();
if(to_addr!=0){
//搬迁成功
returnto_addr;
}
//假如搬迁失利,等候GC作业线程完结搬迁整个page
forwarding->wait_page_released();
}
returnforward_object(forwarding,from_addr);
}
符号

zBarrier.cpp zHeap.inline.cpp

template<boolfollow,boolfinalizable,boolpublish>
uintptr_tZBarrier::mark(uintptr_taddr){
uintptr_tgood_addr;
if(ZAddress::is_marked(addr)){
//假如现已符号过,或Good掩码
good_addr=ZAddress::good(addr);
}elseif(ZAddress::is_remapped(addr)){
//假如remapped,表明GC开端前创立的目标,或Good掩码
//需求符号
good_addr=ZAddress::good(addr);
}else{
//需求remap和符号
good_addr=remap(addr);
}
//符号目标
if(should_mark_through<finalizable>(addr)){
ZHeap::heap()->mark_object<follow,finalizable,publish>(good_addr);
}
if(finalizable){
//假如是可收回目标,则或Finalizable和Good掩码
returnZAddress::finalizable_good(good_addr);
}
returngood_addr;
}
//调用ZHeap的remap目标
uintptr_tZBarrier::remap(uintptr_taddr){
assert(!ZAddress::is_good(addr),"Shouldnotbegood");
assert(!ZAddress::is_weak_good(addr),"Shouldnotbeweakgood");
returnZHeap::heap()->remap_object(addr);
}
//remap目标
inlineuintptr_tZHeap::remap_object(uintptr_taddr){
assert(ZGlobalPhase==ZPhaseMark||
ZGlobalPhase==ZPhaseMarkCompleted,"Forwardnotallowed");
ZForwarding*constforwarding=_forwarding_table.get(addr);
if(forwarding==NULL){
//假如forwardingtable中没有,则无需搬迁
returnZAddress::good(addr);
}
//搬迁目标
//首要是搬迁上一次GC时符号的目标
return_relocate.forward_object(forwarding,ZAddress::good(addr));
}

指针自愈

zBarrier.inline.hpp

template<ZBarrierFastPathfast_path>
inlinevoidZBarrier::self_heal(volatileoop*p,uintptr_taddr,uintptr_theal_addr){
if(heal_addr==0){
return;
}
assert(!fast_path(addr),"Invalidselfheal");
assert(fast_path(heal_addr),"Invalidselfheal");
//死循环
for(;;){
//CASgood指针替换原指针
constuintptr_tprev_addr=Atomic::cmpxchg((volatileuintptr_t*)p,addr,heal_addr);
if(prev_addr==addr){
//CAS成功即可回来
return;
}
if(fast_path(prev_addr)){
//假如fastpath判别为true,则直接回来
return;
}
//走到这儿,可能是指针现已被其他barrier自愈了。
assert(ZAddress::offset(prev_addr)==ZAddress::offset(heal_addr),"Invalidoffset");
addr=prev_addr;
}
}

总的来说,ZGC 的 load barrier 是个十分精巧的规划,凭借 colored pointer 和多视图,有效地避免了 load barrier 带来的功能压力。

参加我们

我们是头绪智能营销团队,担任字节跳动头绪营销型产品研制。致力于帮助百万中小广告主更快速、更高效地获取高价值客户。使用字节的精准流量、数据智能技能,为广告主供给了具有头绪获取、头绪跟进、培育销转和再营销增加闭环的一站式客户头绪办理平台。期待您的参加。

社招:job.toutiao.com/s/LK66Cf7

实习生:job.toutiao.com/s/LK61qf7