G1是JDK9默认的垃圾回收器。想要玩转高版本,高版本的垃圾回收器先了解一下!
本文简单介绍了G1,内存分配策略,GC回收方式,以及G1调优。
G1(garbage—first)是JVM中的一种垃圾回收器,适用于多核、大内存的服务端,garbage-first意思是总是优先回收价值最大的区域。
-XX:+UseG1GC 开启G1垃圾收集器
-Xmx32g 设定堆内存的最大内存为32G
G1的出现是为了替换CMS
注: G1为什么可以建立可预测的停顿时间模型?
G1有计划地避免在整个堆中进行全区域的垃圾收集,G1通过优先列表计算各个region里面的垃圾堆积价值(回收获得的空间以及回收所需时间),每次根据允许的收集时间,优先回收价值最大的Region(Garbage-First名称的来由)。G1使用region划分内存空间和设定优先级的回收方式,保证了有限时间内能高效率的回收垃圾,保证了程序的长时间运行。
Serial GC,Parallel GC,CMS GC将整个堆按年代划分,进行分代回收。G1依然是分代垃圾回收器,G1将堆划分为2048个region(大小为1~32M,2的幂次方),每个region从属不同的年代(注意:region并不固定属于某个年代,有时候属于young,有时候属于old,根据其保存对象来决定),每个年代都是一部分region的集合。如图:
-XX:G1HeapRegionSize=16M 设置reigon区域大小,1-32M之间,划分2048个region
-XX:G1NewSizePercent=5 设置young代的堆空间占比,default:5%
-XX:NewRatio=2 设置young与old的比率,default:2
-XX:SurvivorRatio=8 设置Eden与Survivor的比率,default:8
每个region由若干个Card(512byte,card是堆内存最小可用粒度)构成,一个对象通常会占用一个region的若干个card,GC就是对region的card进行处理。
region的所有card记录在Card Table(byte[])中,通过byte[]下标保存card的地址,每个card默认未被引用,当一个card被引用时,值设为0。
每个Region都有一个Rset纪录其他region对本region的所有引用。通过扫描本region的RSet,来确定对region内的对象进行引用的对象是否存活,进而确定region内对象的存活情况。
Rset底层是Hash table,Key是region的起始地址,Value是Card Table卡表,卡表下标是card卡的地址,存放值为0表示被引用。
G1通过一个增量式的完全标记并发算法,计算region的活跃度,得到准确的region引用信息,不进行整堆扫描(整堆扫描效率低)。
point-out:在CMS中,Rset记录老年代指向新生代的引用。Young GC扫描根时,只需扫描Rset,而不需要扫描整个老年代。
point-in:G1因为region数量太多,有些不需要GC的分区引用会被扫描,point-out会造成大量的扫描浪费。G1使用point-in将当前分区的对象作为根来扫描,没有关联的分区不会被扫描,以此避免无效扫描。
G1设置Humongous区域存储巨型对象(大小超过region50%以上),如果一个Humongous装不下,会寻找连续的Humongous区来存储,找不到能存放巨型对象的连续Humougous区域会强制Full GC。
注意:
空白区,未使用区域。除掉young(Eden,Survivor),old和humongous区剩下的区域。
每个GC线程都有一个PLAB(Promotion Local Allocation Buffer) 。
在young gc中:
MaxTenuringThreshold 来设定晋升年龄 default:15
SATB之前先了解下三色标记法:三色标记算法用来描述追踪式回收器,利用它可以推演回收器的正确性。 三色标记算法将对象分成三种类型:
CMS采用的是增量更新(Incremental update),只要在写屏障(write barrier)里发现要有一个白对象的引用被赋值到一个黑对象 的属性中,那就把这个白对象变成灰色的。即插入的时候记录下来。
G1中使用的是STAB(snapshot-at-the-beginning)并发标记阶段使用的增量式标记算法。并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区。SATB表示GC开始前对存活对象保存快照,并发标记时标记所有快照中当时的存活对象,标记过程中新分配的对象也会被标记为存活对象,删除的时候记录所有对象。
G1默认使用TLAB线程本地分配缓冲区,直到空间不足
Eden分配
eden对象动到Survivor,当Survivor空间不够的时候会直接晋升到Old区。
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
查看相关的GC日志
对象将进入老年代
-XX:PretenureSizeThreshold
将体积大于设定值的对象直接在老年代分配。防止Eden区及两个Survivor之间发生大量的内存复制。
-XX:MaxTenuringThreshold=15 设定晋升老年代的年龄阈值young到old的岁数,default:15
对象从Eden复制到一个survivor,年龄为1,之后每一次Minor GC,age++
注意:当有大批同龄对象占用空间超过Survivor的一半时,这批同龄对象可直接晋升,避免gc的大量复制操作出现。
G1主要有作用于年轻代的Young gc,全堆扫描Full gc和混合收集Mix gc。
先来了解下IHOP阈值:
-XX:InitiatingHeapOccupancyPercent=45 设置IHOP阈值 default:45 heap中占用超过45%,触发mix gc
Eden不再分配新的对象时,触发Young GC,Young GC 回收的是所有年轻代的Region。
Mix GC进行正常的新生代垃圾收集和部分老年代region的收集。
它的GC步骤分2步:
-XX:ParallelGCThreads=8 设置stop-the-world工作线程数,通常和cpu数量一致(max=8),cpu大于8核时,设置为5/8
-XX:ConcGCThreads=10 设置并行标记的线程数为10,default:ParallelGCThreads的1/4。
-XX:MaxGCPauseMillis=200 设置GC的最大暂停时间为200ms
在复制存活对象过程中,会面临内存不足导致无法转移的问题,这个叫转移失败(Evacuation Failure)。转移失败是evacuation无法在堆空间中申请新的region(内存不足),G1被迫执行Full GC(stop-the-world,标记清除压缩算法),对整个堆扫描回收,效率极低。
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,
HandlePromotionFailure 设置是否担保失败
Full GC触发条件:
通过to-space-exhausted、Evacuation Failure查看相关日志,做到尽量避免Full gc的出现(mix gc降级变成使用单线程的serial gc,效率低)。
-XX:G1ReservePercent(默认10%)可以保留空间,来应对晋升模式下的异常情况,最大占用整堆50%。
-XX:InitiatingHeapOccupancyPercent=35 减少IHOP值提前启动标记周期
-XX:ConcGCThreads 增加并发线程数量
老年代的region通常不能在一次stop-the-world暂停阶段被收集完,所以会发起连续多次的混合收集,称为混合收集周期(Mixed Collection Cycle)。
G1会计算年轻代收集停顿时间、每次加入到CSet中的reigon数量以及混合收集次数来确定下次加入CSet的region数量,确定是否结束混合收集周期。
调优主要是为了避免Full gc
首先要学会打印gc日志
-XX:+UseG1GC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
young gc调优
-XX:MaxGCPauseMillis=200
减小数值会触发更多的young gc,带来一系列的问题,导致吞吐量受影响
尽可能根据项目实际生产环境来调试,刚开始建议尽量范错,通过日志来调整,找到最合适的值。
mix gc调优
-XX:InitiatingHeapOccupancyPercent default:45
参数调小,会提前(想对参数较大)触发mix gc周期,频繁进行并发收集会浪费CPU资源(gc没有垃圾可回收导致cpu做无用功)。
参数太高,导致转移空间不足,频繁发生Full gc。
-XX:ConcGCThreads
mix gc周期过长,可增加标记线程的数量提高效率,注意线程数量不是越多越好
-XX:G1MixedGCCountTarget=8 default:8 并发周期中,最多经历几次混合收集周期
参数调小,会增加每次混合收集的region数量,导致stop-the-world时间增加
-XX:G1MixedGCLiveThresholdPercent 存活对象对region占比
注意不要设置新生代的大小,会覆盖暂停时间
-XX:NewRatio=2 设置young与old的比率,default:2