G1垃圾收集器
2020-09-16 10:55:01 1 举报
AI智能生成
G1垃圾收集器调优参数总结
作者其他创作
大纲/内容
调优参数总结
打开G1
-XX:+UseG1GC
-Xms2g -Xmx4g 设置堆大小
-XX:G1HeapRegionSize=16m : 设置HR大小
新生代
设置新生代大小
+Xmn=1g
-XX:NewSize=1024m -XXMaxNewSize=2048m
-XX:NewRatio=9
-XX:G1MaxNewSizePercent=60,-XX:G1NewSizePercent=5
这两个是实验参数,需要开启-XX:+UnlockExperimentalVMOptions才能生效
-XX:GCTimeRatio 配置表示GC与应用的耗时比,默认是9
-XX:G1ExpandByPercentOfAvailable=20 默认值 为20
表示每次都从未提交的内存中申请20%。所有的下限都是1m
表示每次都从未提交的内存中申请20%。所有的下限都是1m
-XX:G1ReservePercent=10 默认值时10
表示在初始化的时候或者内存拓展/收缩的时候保留百分之多少的分区
表示在初始化的时候或者内存拓展/收缩的时候保留百分之多少的分区
期望停顿时间
+XX:MaxGCPauseMillis=10 指定期望停顿时间
+XX:GCPuaseIntervalMillisGC 指定每次GC的时间间隔
+XX:G1ConfidencePercent=0,表示GC预测可信度
值越小表示基于历史数据的预测越准确。
值越小表示基于历史数据的预测越准确。
TLAB
+XX:ObjectAlignmentInBytes=8 ,默认值为8
表示对象对齐的值
表示对象对齐的值
-XX:TLABSize=1m 控制TLAB的大小
-XX:TLABWasteTargetPercent 用于设置TLAB占用Eden空间的百分比,默认值是1%
-XX:TLABRefillWasteFraction 表示线程在TLAB达到多少时需要新开辟TLAB
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
-XX:TLABRefillWasteFraction 表示线程在TLAB达到多少时需要新开辟TLAB
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
JVM提供了-XX:TLABWasteIncrement(默认4)来动态调整TLAB大小
需要限制最小值,JVM提供了+XX:MinTLABSize=2k,默认值是2k,控制TLAB的最小值
-XX:TLABWasteIncrement 表示动态的增加浪费空间的字节数,默认值是4。
一般不用设置,增加该值会增加TLAB浪费的空间。
可以通过-XX:DisableExplicitGC=true 来禁用System.gc()调用
设置了-XX:ExplicitGCInvokesConcurrent 表示可以进行并发的混合回收
-XX:GCLockerRetryAllocationCount=2,表示慢分配之中垃圾回收次数超过这个阈值(尝试分配),就会认为分配失败
Refine线程
-XX:G1ConcRefinementThreads=0
Refine线程池针对处理DCQS的线程数量
默认值0,没有设置就会jvm自动推断,设置为 ParallelGCThreads - 1
ParallelGCThreads的最后一个线程用于处理新生代抽样
-XX:G1UpdateBufferSize=256
增大该值,可以让线程保存更多的引用信息
-XX:G1UseAdaptiveConcRefinement=true
默认值是true,表示可以动态调整 Refinement Zone大小
常常搭配 -XX:G1RSetUpdatingPauseTimePercent使用
-XX:G1RSetUpdatingPauseTimePercent在-XX:G1UseAdaptiveConcRefinement=true的时候生效
默认值是10,表示处理RSet的时间不能超过GC的10%,否则Refinement Zone大小变成原来的0.9。
默认值是10,表示处理RSet的时间不能超过GC的10%,否则Refinement Zone大小变成原来的0.9。
因为这种动态变化可能出现Green = 0,那么会导致 Refine线程不能正常工作,所以有的时候RSet处理太慢需要我们关闭动态Zone,设置合理的RSet
+XX:G1ConcRefinementGreenZone=0
默认值0,jvm自动推断其阈值:ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
+XX:G1ConcRefinementYellowZone=0
默认值0,jvm自动推断其阈值:3 * ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
+XX:G1ConcRefinementRedZone=0
默认值0,jvm自动推断其阈值:6 * ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
-XX:+G1SummarizeRSetStats
打开RSet处理过程中的日志
如果Mutator线程处理的变更较多,可以适当调大Refine线程数
G1SummarizeRSetStats 是 诊断参数,需要加上参数 -XX:+UnlockDiagnosticVMOptions 才能打开
常常搭配 -XX:G1SummarizeRSetStatsPeriod=1 使用
表示每GC发生n次就统计一次
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断,是并行执行GC的线程个数
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
一般不设置该值
-XX:+PrintGCTimeStamps,打印的是时间戳, 10.090
-XX:+PrintGCDateStamps 打印的是日期,2020-09-07T20:58:21.758+0800
YGC参数调优
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断,是并行执行GC的线程个数
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
一般不设置该值
-XX:MaxTenuringThreshold=15
默认值15
发生多少次YGC之后,存活的对象晋升为老生代
-XX:G1RsetScanBlockSize=64
默认值64
扫描RSet的时候,一次处理RSet里面引用的量,如果Cpu能力较强,可以适当增大该值
-XX:ServivorRatio=8
Eden 占新生代的大小比例
默认值8,表示占8成
-XX:TargetSurvivorRatio=50
默认值50
期望的Survivor的大小,增大该值,下次Survivor的空间增大,晋升到老年代的机会小了点(老年代有担保机制)
-XX:ParGCArrayScanChunk=50
默认值50
当一个对象数组进行复制的时候,如果数组长度超过这个阈值之后,不会一次性遍历它,而是分多次处理。每次处理的阈值都是这个值。
其它收集器可能使用,G1一般不适用
-XX:G1EagerReclaimHumongousObjects=true
默认值true
YGC发现回收大对象有性能问题的时候,可以关闭这个选项
-XX:G1EagerReclaimHumongousObjectsWithStaleRefs=true
默认值true
表示大对象RSet关系小于 -XX:G1RSetSparseRegionEntries=0 就尝试回收这个对象
关闭表示RSet关系的引用=0才回收对象,关闭能提高性能,但是牺牲内存。
-XX:+ParallelRefProcEnabled
Ref Proc 引用处理期间时间很长的时候,需要打开这个开关
PLAB
-XX:ResizePLAB=true
基准测试之中可以使用
生成一般不适用
-XX:YoungPLABSize=4096
新生代缓存大小,64位jvm之中默认为32kb,表示从Eden复制到Survivor时,每次请求16kb作为分配缓存,提高分配效率
增大这个值,分配效率会提高,但是会增加内存碎片,并且Survivor更快耗光
实际挑优之中,经常调小该值
-XX:ParallelGCBufferWastePct=10
默认值10
表示对象从Eden到Survivor或Old区的时候,属于空间如果小于这个比例,且不能丢弃这个PLAB时,申请新的PLAB。
这个值越大,分配的效率越高,内存浪费越严重
-XX:OldPLABSize=1024
默认值1024
老生代PLAB的缓存大小,表示对象从Eden到Old时,申请的缓存,增大它同样会调高分配效率,增加内存碎片
实际调优之中可以适当增大该值,因为老生代通常比较大
MixedGC
-XX:InitiatingHeapOccupationPercent=45
默认值45
如果老生代已经分配的内存+将要分配的内存 > 45%。则进行并发标记。否则就到这一步终止,执行的只是YGC。
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断,是并行执行GC的线程个数
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
一般不设置该值
-XX:ConcGCThreads=-XX:ParallelGcThreads/4
默认为GC线程-XX:ParallelGcThreads/4
并且可以动态调整
如果设置了 -XX:G1MarkingOverheadPercent=0
默认0
ConcGCThreads=ncpus * G1MarkingOverheadPercent * MaxGCPauseMillis / GCPauseIntervalMillis
关闭动态调整
设置了-XX:ConcGCThreads,并且-XX:ForceDynamicNumberOfGCThreads=true(默认false)会关闭动态调整
设置-XX:UseDynamicNumberOfGCThreads,默认false。
打开动态调整,使用-XX:TraceDynamicGCThreads可以输出线程并发线程情况。
这个线程数,不能大于ParallelGCThreads
最小值1
-XX:G1ConcMarkStepDurationMillis=10
表示并发标记的目标是在10ms内完成。
FullGC
基本概念
分区 (Heap Region)
自由分区(Free Heap Region,FHR)
新生代分区(Young Heap Region,YHR)
Eden
Servivor
大对象分区(Humongous Heap Region,HHR)
大对象头分区
大对象连续分区
老生代分区(Old Heap Region,OHR)
G1之中每个HR的大小都相同,如何设置大小?
默认的大小
启发式推断,在不指定大小的时候,使用启发式推断HR的大小
设置的最大内存大小:-Xmx,设置最小内存-Xms
( ((最大内存+最小内存)mb / 2 ) / 2048 , min_zise=1mb)
比如: -Xmx6144m -Xms2048m
average_heap_size=(6144m+2048m)/2=4096m
region_size=max(4096m/2048, 1m)=2m
region_size_log=21(因为2^21=2*1024*1024<=2m)
region_size=2^21=2m(保证region_size的值为2^n)
region_size=2m(因为MIN_REGION_SIZE<=2m<=MAX_REGION_SIZE)
( ((最大内存+最小内存)mb / 2 ) / 2048 , min_zise=1mb)
比如: -Xmx6144m -Xms2048m
average_heap_size=(6144m+2048m)/2=4096m
region_size=max(4096m/2048, 1m)=2m
region_size_log=21(因为2^21=2*1024*1024<=2m)
region_size=2^21=2m(保证region_size的值为2^n)
region_size=2m(因为MIN_REGION_SIZE<=2m<=MAX_REGION_SIZE)
参数设置: -XX:G1HeapRegionSize=16m
这个值默认是0
假设配置JVM参数-Xmx1024m -Xms1024m -XX:G1HeapRegionSize=4m,那么计算过程如下:
region_size=4m
region_size_log=22(因为2^22<=4m)
region_size=2^22=4m
region_size=4m(因为MIN_REGION_SIZE<=1m<=MAX_REGION_SIZE)
假设配置JVM参数-Xmx1024m -Xms1024m -XX:G1HeapRegionSize=4m,那么计算过程如下:
region_size=4m
region_size_log=22(因为2^22<=4m)
region_size=2^22=4m
region_size=4m(因为MIN_REGION_SIZE<=1m<=MAX_REGION_SIZE)
G1最大管理128G内存
HR大小在[1MB, 32MB]之间,并且出1之外,一定要是2的倍数
HR个数最多是4096个
新生代大小
新生代最大值:MaxNewSize,最小值NewSize
配置例子:-XX:NewSize=1024m -XX:MaxNewSize=2014M
使用参数:+Xmn=1g;同时设置了最大值,最小值。都是1gb
NewRatio
设置了最大值,最小值,NewRatio就无效
没有设置最大最小值,新生代的最大/最小值相同,为:(整个堆大小)/(NewRatio + 1)
-XX:NewRatio=9
G1MaxNewSizePercent,G1NewSizePercent
如果没有设置最大值/最小值,或者只设置了一个,那么G1将根据G1MaxNewSizePercent,G1NewSizePercent来推断新生代的大小
默认值:G1MaxNewSizePercent=60,G1NewSizePercent=5
注意点:如果G1推断的新生代最大值/最小值相等(即不会动态变化),有可能会出现 G1停顿时间不满足期望的情况。
实现方式:一张分区列表
1. 使用一张分区列表,扩张的时候如果有空闲列表则之江把空闲分区加入到新生代分区列表之中
2. 如果没有,则分配新的分区,如何把它加入到新生代分区列表之中
新生代的动态拓展
何时拓展
-XX:GCTimeRatio 配置表示GC与应用的耗时比,默认是9
计算方式:100 * (1.0 / (1.0 + GCTimeRatio)) %
比如默认值是9,那么就是G1 GC的时间和用户线程的时间占比不超过 10%时不需要动态扩展
比如默认值是9,那么就是G1 GC的时间和用户线程的时间占比不超过 10%时不需要动态扩展
一次拓展多少内存
-XX:G1ExpandByPercentOfAvailable=20 默认值 为20
表示每次都从未提交的内存中申请20%。所有的下限都是1m
表示每次都从未提交的内存中申请20%。所有的下限都是1m
-XX:G1ReservePercent=10 默认值时10
表示在初始化的时候或者内存拓展/收缩的时候保留百分之多少的分区
表示在初始化的时候或者内存拓展/收缩的时候保留百分之多少的分区
G1停顿预测模型
预测逻辑
衰减平均值
衰减标准差
可以使用+XX:MaxGCPauseMillis=10 指定期望停顿时间
可以使用+XX:GCPuaseIntervalMillisGC 指定每次GC的时间间隔
默认值是0,启发式推断为MaxGCPauseMilis+1。
这个值必须大于MaxGCPauseMillis
这个值必须大于MaxGCPauseMillis
+XX:G1ConfidencePercent=0,表示GC预测可信度
值越小表示基于历史数据的预测越准确。
值越小表示基于历史数据的预测越准确。
大对象
HR大小的一半及以上
卡表 & 位图
卡表就是CMS之中的CSet,G1还引入了RSet
CSet: Collection Set,面对所有的堆收集垃圾,是整个jvm的回收集
Collection Sets 简称 CSets, 在一次GC中将被执行垃圾回收的HR集合。 GC时在CSet中的所有存活数据(live data)都会被转移(复制/移动)。 集合中的heap区可以是 Eden, Survivor, 和 old generation. CSets所占用的JVM内存小于1%。
示意图,CSet主要分为2种。一种是YHR的,一种是YHR+部分OHR/HHR的。
年轻代收集集合
年轻代收集集合 CSet of Young Collection
应用线程不断活动后,年轻代空间会被逐渐填满。当JVM分配对象到Eden区域失败(Eden区已满)时,便会触发一次STW式的年轻代收集。在年轻代收集中,Eden分区存活的对象将被拷贝到Survivor分区;原有Survivor分区存活的对象,将根据任期阈值(tenuring threshold)分别晋升到PLAB中,新的Survivor分区和老年代分区。而原有的年轻代分区将被整体回收掉。
应用线程不断活动后,年轻代空间会被逐渐填满。当JVM分配对象到Eden区域失败(Eden区已满)时,便会触发一次STW式的年轻代收集。在年轻代收集中,Eden分区存活的对象将被拷贝到Survivor分区;原有Survivor分区存活的对象,将根据任期阈值(tenuring threshold)分别晋升到PLAB中,新的Survivor分区和老年代分区。而原有的年轻代分区将被整体回收掉。
混合收集集合
混合收集集合 CSet of Mixed Collection
年轻代收集不断活动后,老年代的空间也会被逐渐填充。当老年代占用空间超过整堆比IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动一次混合垃圾收集周期。为了满足暂停目标,G1可能不能一口气将所有的候选分区收集掉,因此G1可能会产生连续多次的混合收集与应用线程交替执行,每次STW的混合收集与年轻代收集过程相类似。
年轻代收集不断活动后,老年代的空间也会被逐渐填充。当老年代占用空间超过整堆比IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动一次混合垃圾收集周期。为了满足暂停目标,G1可能不能一口气将所有的候选分区收集掉,因此G1可能会产生连续多次的混合收集与应用线程交替执行,每次STW的混合收集与年轻代收集过程相类似。
RSet: Remember Set,每个HR都有一个,记录老生代到新生代,老生代到老生代的引用
位图是实现,bitmap
对象头
Java对象在JVM之中的描述分为三大部分
1. 对象头
2. 实例数据
3. 对齐填充
对象头分两部分
1. 标记信息 MarkOop
动态储存数据,方便记录更多信息的同时节约空间
32位大小,2位标志位表示锁状态
2. 元数据信息
这个指向方法区/元空间中JVM对对象的描述
Klass,Java对象的创建都需要通过元数据获得。
内存分配与管理
Jvm通过操作系统的系统调用进行内存申请的
操作系统对内存的分配管理典型分为两个阶段
1. 保留
2. 提交
JVM常见的对象类型有6种
1. ResourceObj
每个线程都有一个资源空间,就是这个
2. StackObj
栈对象
3. ValueObj
值对象,该对象在堆对象之中进行嵌套的时候使用
4. AllStaic
全局对象,静态对象,只有一个。
5. MetaspaceObj
元对象,类似InstanceKlass这样的元数据就是元对象
6. CHeapObj
堆对象,由new/delete/free/malloc管理。其中包含的对象很多比如Java对象。
线程
JVM之中的线程分类
Thread
1. JavaThread
1. CompilerThread
2. NamedThread
1. WorkerThread
1. GangWorker
2. GCTaskThread
2. VMThread
3. ConcurrentGCThread
3. WatcherThread
所有的这些Thread都会对应一个OsThread(操作系统线程)
栈帧
Java栈帧:在线程执行时和运行的过程之中保存线程的上下文
垃圾回收的根
不同的CPU之中,结构不同。
在GC之中,我们通过第一步就是遍历根,Java线程的栈帧就是根元素之一。
我们通常将Java的栈帧作为根来遍历堆,对对象进行并行标记并收集垃圾。
我们通常将Java的栈帧作为根来遍历堆,对对象进行并行标记并收集垃圾。
句柄
HandlerArea:一个线程资源区,记录JVM内部线程对堆对象的引用。这里分配句柄。
句柄的生命周期:HandleMark
所有的句柄形成一个链表,这样就可以获取本地代码执行中对堆对象的引用。
针对本地方法栈有专门的句柄池:JNIHandle
分两种
1. 全局对象引用
2. 局部对象引用
G1垃圾收集器提供了三种收集垃圾的算法
1. 新生代回收
针对所有的新生代分区
2. 混合回收
针对所有新生代,部分老生代
3. Full GC
针对所有的分区
垃圾回收算法都是遍历Root节点,标记哪些内存没用来回收内存的。所以遍历得越多,效率就会越慢
记忆集
概念
记忆集是一种抽象概念,记录对象在不同的代际之间的引用关系,目的是为了加速垃圾回收的速度
RSet记录的数据
1. 老生代到新生代之间的引用
2. 老生代到老生代之间的引用
RSet记录引用的方式
Point in
分区之间的引用关系
1. 分区内部的引用
RSet不会记录,因为GC是针对一个分区的
2. 新生代到新生代之间的引用
RSet不会记录,因为三种垃圾回收方式都会遍历新生代
3. 新生代到老生代之间的引用
RSet不会记录,因为三种垃圾回收方式都会遍历新生代
4. 老生代到新生代之间的引用
RSet会记录这个,因为YGC的时候,需要知道哪些新生代被引用了
5. 老生代到老生代之间的引用
RSet会记录这个,因为混合GC的时候,可能只有部分分区被回收,需要记录引用信息。
RSet和HR的关系
RSet记录在HR之中。
RSet和卡表之间的关系
卡表是一个全局表,这个卡表记录该HR中的对象垃圾回收过程中的状态信息。而且能描述对象所处的内存区域块,它能快速描述内存的使用情况
RSet定位引用对象所在HR的块
RSet变更的两种方式
1. Refine 线程更新
2. 写栅栏 更新
栅栏 Barrier
我们首先介绍一下栅栏(Barrier)的概念。栅栏是指在原生代码片段中,当某些语句被执行时,栅栏代码也会被执行。而G1主要在赋值语句中,使用写前栅栏(Pre-Write Barrrier)和写后栅栏(Post-Write Barrrier)。事实上,写栅栏的指令序列开销非常昂贵,应用吞吐量也会根据栅栏复杂度而降低。
写前栅栏 Pre-Write Barrrier
即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,那么等式左侧对象原先引用的对象所在分区将因此丧失一个引用,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象。JVM并不会立即维护RSet,而是放入SATB队列中。SATB在Mixed GC的时候就需要被处理了。
写后栅栏 Post-Write Barrrier
当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,那么等式右侧对象所在分区的RSet也应该得到更新。同样为了降低开销,写后栅栏发生后,RSet也不会立即更新,同样只是记录此次更新自己的DCQ或者全局DCQS,在将来批量处理(见Concurrence Refinement Threads)。
我们首先介绍一下栅栏(Barrier)的概念。栅栏是指在原生代码片段中,当某些语句被执行时,栅栏代码也会被执行。而G1主要在赋值语句中,使用写前栅栏(Pre-Write Barrrier)和写后栅栏(Post-Write Barrrier)。事实上,写栅栏的指令序列开销非常昂贵,应用吞吐量也会根据栅栏复杂度而降低。
写前栅栏 Pre-Write Barrrier
即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,那么等式左侧对象原先引用的对象所在分区将因此丧失一个引用,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象。JVM并不会立即维护RSet,而是放入SATB队列中。SATB在Mixed GC的时候就需要被处理了。
写后栅栏 Post-Write Barrrier
当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,那么等式右侧对象所在分区的RSet也应该得到更新。同样为了降低开销,写后栅栏发生后,RSet也不会立即更新,同样只是记录此次更新自己的DCQ或者全局DCQS,在将来批量处理(见Concurrence Refinement Threads)。
RSet的数据结构
每个HR都包含一个PRT的数据结构,记录HR中被其它对象修改的引用。
PRT分三种
1. 稀疏PRT,就是哈希表
2. 细粒度PRT,就是数组
3. 粗粒度PRT,就是位图
RSet的数据结构就是PRT
-XX:G1TraceHeapRegionRememberedSet=true
打开记忆集详细信息,只在debug模式的vm能打开
-XX:G1HRRSUseSparseTable=true
RSet使用稀疏矩阵信息打印,只在debug模式的vm能打开
-XX:G1RecordHRRSOops=true
RSet使用对象指针,只在debug模式的vm能打开
内存分配
快速分配TLAB
TLAB默认是打开的,需要关闭的话使用-XX:-UseTLAB
实现原理指针碰撞
-XX:TLABSize=1m 控制TLAB的大小
-XX:TLABWasteTargetPercent 用于设置TLAB占用Eden空间的百分比,默认值是1%
那么TLABSize= (Eden * 2 * 1%) / 线程个数(乘以2是因为假设其内存使用服从均匀分布)
那么TLABSize= (Eden * 2 * 1%) / 线程个数(乘以2是因为假设其内存使用服从均匀分布)
实际生成中,可以适当增大该值
-XX:TLABRefillWasteFraction 表示线程在TLAB达到多少时需要新开辟TLAB
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
默认值是64,表示TLAB的可用空间只剩1/64的时候,分配新的TLAB。
这里有一个refill_waste的值,refill_waste=1/TLABRefilWasteFraction,表示少于这个值的时候开始申请新的TLAB
如果refill_waste的值过大,说明内存浪费严重,需要适当调小
-XX:TLABWasteIncrement 表示动态的增加浪费空间的字节数,默认值是4。
一般不用设置,增加该值会增加TLAB浪费的空间。
TLAB大小的动态调整
JVM提供了-XX:TLABWasteIncrement(默认4)来动态调整TLAB大小
需要限制最小值,JVM提供了+XX:MinTLABSize=2k,默认值是2k,控制TLAB的最小值
需要限制最大值,JVM认为HeapRegion/2以上的大小都是大对象,不会进入Eden,所以TLAB的最大值是HeapRegion/2
如果想禁用TLAB的自动调整大小
-XX:-ResizeTLAB禁用ResizeTLAB。
并使用-XX:TLABSize手动指定一个TLAB大小
一般不推荐,推荐使用虚拟机默认的
具体步骤:
1. 快速分配,分配成功则直接返回
2. TLAB的慢速分配,在快速分配失败之后进行
流程图:https://www.processon.com/view/5f45274cf346fb3c83c61abb
-XX:+PrintTLAB可以打印TLAB的使用情况。
慢速分配
jvm经过努力还是不能在TLAB之中分配内存,就会进入慢速分配,就是下面的慢速分配
1. 检查是否是大对象,大对象直接分配到老生代,成功则返回。
2. 不是巨型对象,直接尝试对象分配,成功则返回。
3. 分配不成功,则进行GC垃圾回收,这里就会进行Full GC。
4. 尝试分配,回到步骤1。达到一定的次数,就会分配失败。
2. 不是巨型对象,直接尝试对象分配,成功则返回。
3. 分配不成功,则进行GC垃圾回收,这里就会进行Full GC。
4. 尝试分配,回到步骤1。达到一定的次数,就会分配失败。
-XX:GCLockerRetryAllocationCount=2,表示慢分配之中垃圾回收次数超过这个阈值(尝试分配),就会认为分配失败
G1垃圾回收的时机
1. 内存不足
2. Java代码调用System.gc()
可以通过-XX:DisableExplicitGC=true 来禁用System.gc()调用
设置了-XX:ExplicitGCInvokesConcurrent 表示可以进行并发的混合回收
垃圾回收复制算法YGC使用的PLAB分配对象
PLAB:Promotion Local Allocate Buffer
G1的Refine线程
Refine线程
概念
Refine中文翻译: 提纯,改进
Refine线程是一个vm内部的并发线程池,线程数目为:G1ConcRefinementThreads + 1
功能
1. 处理新生代的抽样,在满足响应时间的情况下,更新YHR的数目。通常Refine线程池会专门使用一个线程处理这个
抽样的目的是:预测停顿时间并调整HR数目
-XX:G1ConcRefinementServiceIntervalMillis=300
默认值300,表示RS对新生代的抽样时间为300ms
2. 管理RSet,这个是主要功能
运行步骤
0号Refine线程启动
1. 整个jvm有一个全局的静态变量DirtyCardQueueSet(DCQS),对这个变量操作需要加锁。
2. 每个线程都有自己的DirtyCardQueue(DCQ),这个DCQ的最大长度是由G1UpdateBufferSize(默认256)决定。
当线程填对象引用关系到自己的DCQ满了之后,就会把这个DCQ放到全局变量DCQS中管理
当线程填对象引用关系到自己的DCQ满了之后,就会把这个DCQ放到全局变量DCQS中管理
-XX:G1UpdateBufferSize=256
3. 当DCQS之中的DCQ数量大于阈值时,有一个monitor会通知0号Refine线程启动。然后0号线程去更新RSet
4. 如果DCQ进入DCQS的时候,发现DCQS已经满了,那么就会暂停任务执行,而自己执行RSet的更新,减少Refine线程的压力
0号线程发现自己太忙,激活1号Refine线程
1号线程发现自己太忙,激活2号Refine线程
...
Refine线程主要工作是处理DCQS
整个更新的一句话概括就是: G1 采用Point in的做法。根据引用者,找到被引用者,然后在被引用者所在分区的RSet中记录引用关系
RSet使用写屏障来记录RSet的引用变更关系
每次老生代对新生代对象/老生代对象的引用变更都会需要通过写屏障,所以Refine线程可以通过写屏障捕获老生代对象变更
捕获到写屏障之后,将引用关系插入DCQ之中。
-XX:+G1SummarizeRSetStats
打开RSet处理过程中的日志
如果Mutator线程处理的变更较多,可以适当调大Refine线程数
G1SummarizeRSetStats 是 诊断参数,需要加上参数 -XX:+UnlockDiagnosticVMOptions 才能打开
常常搭配 -XX:G1SummarizeRSetStatsPeriod=1 使用
表示每GC发生n次就统计一次
Refinement Zone
DCQS分配四个区
白区
[0, Green) ,在该区Refine线程空转,不处理任何东西,交给GC线程处理DCQ,YGC会处理
绿区
[Green, Yellow), 在该区Refiine线程开始启动,并且根据DCQS的数值大小按比例启动不同数量的Refine线程处理DCQ
黄区
[Yellow, Red),在该区,所有的Refine线程都参与DCQ的处理
红区
[Red, +),这个区表示,不仅Refine线程参与处理DCQ,而且Mutator(用户线程)也要参与处理DCQ。
在上面四个区有三个关键字,Green, Yellow, Red
分别使用三个参数设置
分别使用三个参数设置
+XX:G1ConcRefinementGreenZone=0
默认值0,jvm自动推断其阈值:ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
+XX:G1ConcRefinementYellowZone=0
默认值0,jvm自动推断其阈值:3 * ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
+XX:G1ConcRefinementRedZone=0
默认值0,jvm自动推断其阈值:6 * ParallelGCThreads
需要 -XX:G1UseAdaptiveConcRefinement=false
-XX:G1ConcRefinementThreads=0
Refine线程池针对处理DCQS的线程数量
默认值0,没有设置就会jvm自动推断,设置为 ParallelGCThreads - 1
ParallelGCThreads的最后一个线程用于处理新生代抽样
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
-XX:G1ConcRefinementThresholdStep=G1ConcRefinementThreads+1
G1ConcRefinementThresholdStep表示步长的意思,用于推断DCSQ大小多少启动多少Refine线程
黄区个数 - 绿区的个数 / (worknum + 1)
-XX:G1UseAdaptiveConcRefinement=true
默认值是true,表示可以动态调整 Refinement Zone大小
常常搭配 -XX:G1RSetUpdatingPauseTimePercent使用
-XX:G1RSetUpdatingPauseTimePercent在-XX:G1UseAdaptiveConcRefinement=true的时候生效
默认值是10,表示处理RSet的时间不能超过GC的10%,否则Refinement Zone大小变成原来的0.9。
默认值是10,表示处理RSet的时间不能超过GC的10%,否则Refinement Zone大小变成原来的0.9。
因为这种动态变化可能出现Green = 0,那么会导致 Refine线程不能正常工作,所以有的时候RSet处理太慢需要我们关闭动态Zone,设置合理的RSet
RSet的空间
-XX:G1ConcRSLogCacheSize=10
-XX:G1ConcRSHotCardLimit=4
默认值4,表示一个card被修改了4次,就是hot card
-XX:G1RSetRegionEntries=0
YGC
采用复制算法
YGC会把所有的新生代的分区加入到CSet之中
分为两部分
并行部分
1. 处理根
根扫描,扫描结果处理
1)对象没有设置过标识信息,把对象从Eden复制到Survivor
2)复制到Survivor的对象的field,如果field在CSet之中,则把对象的地址加到G1ParScanThreadState (PSS)的队列等待扫描,不在CSet之中,则更新对象所在堆分区对应的RSet之中。
1)对象没有设置过标识信息,把对象从Eden复制到Survivor
2)复制到Survivor的对象的field,如果field在CSet之中,则把对象的地址加到G1ParScanThreadState (PSS)的队列等待扫描,不在CSet之中,则更新对象所在堆分区对应的RSet之中。
2. 把RSet作为根处理,以及处理DCQS中剩下的dcq。
处理老生代分区到新生代分区的引用
1)处理Dirty Card,更新RSet,更新老生代分区到新生代分区的引用
2)扫描RSet,把引用者作为根,从根出发,对可达对象进行根扫描
3)复制,在PSS队列中的对象都是活跃对象,每个都要复制到Survivor区。在扫描该对象的field,重复上面第一步。直到PSS队列没有对象。
1)处理Dirty Card,更新RSet,更新老生代分区到新生代分区的引用
2)扫描RSet,把引用者作为根,从根出发,对可达对象进行根扫描
3)复制,在PSS队列中的对象都是活跃对象,每个都要复制到Survivor区。在扫描该对象的field,重复上面第一步。直到PSS队列没有对象。
3. 复制对象
其它部分
4. Redirty
5. 清除空间,设置新分区属性等等
6. 根据时间停顿模型,设置停顿时间来控制新生代HR的数目。
最小值
Survivor长度+1
最大值
新生代最大分区数目或者除去保留空间的最大自由空间数目
执行步骤
根处理
选择收集集合的CSet,这里选择Young GC 的CSet。用来存放收集存活对象的引用。
扫描根,从根出发,将根下所有的活跃对象复制到新的分区,同时将活跃对象的field加入到一个栈之中
修改被复制的对象的对象头,最后两位为11,表示这个对象已经被标记过了
RSet处理
把RSet当成根,扫描活跃对象,更新指针指到复制对象
复制
这里的复制和根的复制不一样,这里的复制指的是那些没被CSet收集到的对象,但是被对象的field直到的对象,并且这个对象在新生代之中。这个时候就需要复制到这个对象到新的分区。
在复制的时候,因为是多线程复制,可能出现Survivor空间不足,需要Old空间担保,担保的方式就是PLAB分配。PLAB:Promotion local allocate buffer。
PLAB在YGC的时候使用,复制到Survivor或者老生代的时候,做担保,让对象能成功快速复制。
PLAB在YGC的时候使用,复制到Survivor或者老生代的时候,做担保,让对象能成功快速复制。
Redirty
更新RSet,保证引用关系的正确性。比如老生代的一个对象指向新生代一个对象,但是这个对象被移动到另外一个地方了,那么就需要更新RSet了。
释放空间
将复制到的分区设置为Survivor分区,清空原来的分区,等操作。
Java根分类
1. Java根
类加载器
YGC线程会遍历类加载器中所有的存活的Klass并复制到Survivor或者晋升到老年代
线程栈
1. 处理普通的Java线程访问的堆
2. 处理本地方法栈访问的堆
2. JVM根
全局对象
JNIHandles
StringTable
日志解读
案例
YGC参数调优
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断,是并行执行GC的线程个数
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
一般不设置该值
-XX:MaxTenuringThreshold=15
默认值15
发生多少次YGC之后,存活的对象晋升为老生代
-XX:G1RsetScanBlockSize=64
默认值64
扫描RSet的时候,一次处理RSet里面引用的量,如果Cpu能力较强,可以适当增大该值
-XX:ServivorRatio=8
Eden 占新生代的大小比例
默认值8,表示占8成
-XX:TargetSurvivorRatio=50
默认值50
期望的Survivor的大小,增大该值,下次Survivor的空间增大,晋升到老年代的机会小了点(老年代有担保机制)
-XX:ParGCArrayScanChunk=50
默认值50
当一个对象数组进行复制的时候,如果数组长度超过这个阈值之后,不会一次性遍历它,而是分多次处理。每次处理的阈值都是这个值。
其它收集器可能使用,G1一般不适用
-XX:G1EagerReclaimHumongousObjects=true
默认值true
YGC发现回收大对象有性能问题的时候,可以关闭这个选项
-XX:G1EagerReclaimHumongousObjectsWithStaleRefs=true
默认值true
表示大对象RSet关系小于 -XX:G1RSetSparseRegionEntries=0 就尝试回收这个对象
关闭表示RSet关系的引用=0才回收对象,关闭能提高性能,但是牺牲内存。
-XX:+ParallelRefProcEnabled
Ref Proc 引用处理期间时间很长的时候,需要打开这个开关
PLAB
-XX:ResizePLAB=true
基准测试之中可以使用
生成一般不适用
-XX:YoungPLABSize=4096
新生代缓存大小,64位jvm之中默认为32kb,表示从Eden复制到Survivor时,每次请求16kb作为分配缓存,提高分配效率
增大这个值,分配效率会提高,但是会增加内存碎片,并且Survivor更快耗光
实际挑优之中,经常调小该值
-XX:ParallelGCBufferWastePct=10
默认值10
表示对象从Eden到Survivor或Old区的时候,属于空间如果小于这个比例,且不能丢弃这个PLAB时,申请新的PLAB。
这个值越大,分配的效率越高,内存浪费越严重
-XX:OldPLABSize=1024
默认值1024
老生代PLAB的缓存大小,表示对象从Eden到Old时,申请的缓存,增大它同样会调高分配效率,增加内存碎片
实际调优之中可以适当增大该值,因为老生代通常比较大
Mixed GC 混合回收
为什么会有混合回收——因为一般老年代都比较大,那么整个老年代都回收性能会比较差,所以我们针对老年代设计了部分回收
Mixed GC主要的两个阶段
1. 并发标记
识别老年代分区中的活跃对象,计算分区中的垃圾对象占空间多少(用于垃圾回收阶段判断是否回收这个分区)
分三步/三个子阶段
1. 初始标记:
这一阶段需要STW
使用YGC之后的Survivor为根,收集老生代对新生代的引用结果。就是新生代的GC,只是借用了YGC的结果,MixedGC肯定在YGC之后。
其实严格计较起来这一步算在YGC之中,不能算在Mixed GC之中。不知道为什么大多数资料都是算在Mixed GC之中。感觉是直接翻译国外的文章一样。
这一阶段需要STW
使用YGC之后的Survivor为根,收集老生代对新生代的引用结果。就是新生代的GC,只是借用了YGC的结果,MixedGC肯定在YGC之后。
其实严格计较起来这一步算在YGC之中,不能算在Mixed GC之中。不知道为什么大多数资料都是算在Mixed GC之中。感觉是直接翻译国外的文章一样。
其实YGC的时候也会从GC Roots出发,扫描OHR的引用和HHR的对象引用。只是扫描到了这个对象但是不去做标记。
而Mixed GC是建立在YGC之上的,当YGC得知这次GC会有Mixed GC的时候,会扫描到OHR/HHR并标记。否则YGC扫描到老对象和巨型对象都不会标记,而是忽略。
Mixed GC是在并发标记之后,通知下次YGC之中,然后并发标记,并且回收空间。并不是并发标记之后立刻回收内存。
而Mixed GC是建立在YGC之上的,当YGC得知这次GC会有Mixed GC的时候,会扫描到OHR/HHR并标记。否则YGC扫描到老对象和巨型对象都不会标记,而是忽略。
Mixed GC是在并发标记之后,通知下次YGC之中,然后并发标记,并且回收空间。并不是并发标记之后立刻回收内存。
2. 并发标记:
这一阶段是Muatator线程和GC线程并行执行的,称为并发标记阶段。
职责是:
根据初始标记(YGC)的结果,扫描Miexed GC的CSet即Java 根/Survivor/部分OHR和针对老年代的RSet并发标记。
这里有个逻辑判断:-XX:InitiatingHeapOccupationPercent=45
XX:InitiatingHeapOccupationPercent默认值45表示
如果老生代已经分配的内存+将要分配的内存 > 45%。则进行并发标记。否则就到这一步终止,执行的只是YGC。
如果触发了并发标记, -XX:ConcGCThreads(默认为GC线程-XX:ParallelGcThreads/4)可以指定有多少个线程用于扫描。
扫描时一个分区一个分区地扫描,标记活的对象。统计活跃的数量,最后把结果放入CSet之中。
这一阶段是Muatator线程和GC线程并行执行的,称为并发标记阶段。
职责是:
根据初始标记(YGC)的结果,扫描Miexed GC的CSet即Java 根/Survivor/部分OHR和针对老年代的RSet并发标记。
这里有个逻辑判断:-XX:InitiatingHeapOccupationPercent=45
XX:InitiatingHeapOccupationPercent默认值45表示
如果老生代已经分配的内存+将要分配的内存 > 45%。则进行并发标记。否则就到这一步终止,执行的只是YGC。
如果触发了并发标记, -XX:ConcGCThreads(默认为GC线程-XX:ParallelGcThreads/4)可以指定有多少个线程用于扫描。
扫描时一个分区一个分区地扫描,标记活的对象。统计活跃的数量,最后把结果放入CSet之中。
-XX:InitiatingHeapOccupationPercent=45
默认值45
如果老生代已经分配的内存+将要分配的内存 > 45%。则进行并发标记。否则就到这一步终止,执行的只是YGC。
-XX:ConcGCThreads=-XX:ParallelGcThreads/4
默认为GC线程-XX:ParallelGcThreads/4
并且可以动态调整
如果设置了 -XX:G1MarkingOverheadPercent=0
默认0
ConcGCThreads=ncpus * G1MarkingOverheadPercent * MaxGCPauseMillis / GCPauseIntervalMillis
关闭动态调整
设置了-XX:ConcGCThreads,并且-XX:ForceDynamicNumberOfGCThreads=true(默认false)会关闭动态调整
设置-XX:UseDynamicNumberOfGCThreads,默认false。
打开动态调整,使用-XX:TraceDynamicGCThreads可以输出线程并发线程情况。
这个线程数,不能大于ParallelGCThreads
-XX:G1ConcMarkStepDurationMillis=10
表示并发标记的目标是在10ms内完成。
3. 最终标记/再标记子阶段/Remark
这一阶段需要STW
职责是:
检查并发标记的任务是否完成,没完成就要STW完成:1)通过Survivor分区追踪所有的老年区活跃对象,放入Mixed GC的CSet中。这是并发标记的职责,到这里要结束掉它。
2)因为并发标记阶段是Mutator和GC线程并行运行的,所以会产生新的引用变更,这些变更使用SATB(Snapshot At The Beginning)的方式处理,新增的引用都是活跃对象。SATB和写前栅栏有关。
这里需要把SATB队列的引用关系都处理完,循环处理知道SATB队列为空。所以需要STW,否则一边清理引用,一边创建引用,永远也没办法清理完。
这个阶段可以通过-XX:ParallelGCThreads=0 设置STW的时候最终标记的线程数。
这一阶段需要STW
职责是:
检查并发标记的任务是否完成,没完成就要STW完成:1)通过Survivor分区追踪所有的老年区活跃对象,放入Mixed GC的CSet中。这是并发标记的职责,到这里要结束掉它。
2)因为并发标记阶段是Mutator和GC线程并行运行的,所以会产生新的引用变更,这些变更使用SATB(Snapshot At The Beginning)的方式处理,新增的引用都是活跃对象。SATB和写前栅栏有关。
这里需要把SATB队列的引用关系都处理完,循环处理知道SATB队列为空。所以需要STW,否则一边清理引用,一边创建引用,永远也没办法清理完。
这个阶段可以通过-XX:ParallelGCThreads=0 设置STW的时候最终标记的线程数。
-XX:ParallelGCThreads=0
默认值0,没设置,jvm就会自动推断,是并行执行GC的线程个数
ParallelGCThreads = ncpus
当 ncpus <= 8,ncpus 为 8 + (cpu个数 - 8)*5/8
当 ncpus > 8, ncpus 为cpu内核的个数
一般不设置该值
-XX:G1SATBBufferSize-1
默认SATB
清理子阶段
4. 清理子阶段
这一阶段也需要STW
职责是:
统计Old Gen的存活对象,然后排序。用来做CSet选择。SATB和新分配的对象都放置到CSet中。
清理RSet处理RSet的引用修正。
把遇到的空闲分区放入空闲分区列表。
这一步其实是为了Mixed GC的回收阶段做最后准备。
这一阶段也需要STW
职责是:
统计Old Gen的存活对象,然后排序。用来做CSet选择。SATB和新分配的对象都放置到CSet中。
清理RSet处理RSet的引用修正。
把遇到的空闲分区放入空闲分区列表。
这一步其实是为了Mixed GC的回收阶段做最后准备。
其实目的就是 让垃圾回收的时候,理解老年代的内存使用情况。最终的结果是在RSet,CSet之中。
2. 垃圾回收
这个阶段的流程和新生代的一样,重用了新生代的代码
不同的地方是会回收并发标记中识别到的垃圾多的老生代分区
不同的地方是会回收并发标记中识别到的垃圾多的老生代分区
最终的流程图可以参考我的作品
Mixed GC在YGC之后借助YGC的Survivor走后续的流程。
Full GC
0 条评论
下一页