JDK 11中低延迟垃圾回收器,设计目标:
停顿时间 < 10ms,不随堆、活跃对象的大小而增加
支持8MB~4TB的堆(未来支持16TB)
概要:GC之痛(CMS和G1停顿时间瓶颈) 、ZGC原理(更短)、ZGC调优实践、升级ZGC效果
一、GC之痛
可用性99.99%未达到:CMS单次Young GC 40ms,一分钟10次,接口响应时间30ms。( 40ms + 30ms ) * 10次 / 60000ms = 1.12%
四个STW:两次标记、复制(耗时长)、清理。 ps:G1和CMS的Young GC,标记-复制全过程STW
二、ZGC原理
标记、转移和重定位阶段几乎都并发
1、三个STW:1\2)初始标记和转移(短):,只扫描所有GC Roots,处理时间和GC Roots数量成正比 3)再标记(短):最多1ms,超过1ms再次进入并发标记。
STW只依赖GC Roots大小,和堆或者活跃对象没关系
2、ZGC关键技术
着色指针和读屏障,实现并发转移,转移过程准确访问对象。
原理:1)old:转移时,应用线程不停访问对象。可能旧地址造成错误。2)ZGC:访问触发“读屏障”,发现对象移动(着色指针判断),“读屏障”更到对象新地址上
(1)着色指针
将信息存在指针中,ZGC仅支持64位系统,把64位虚拟地址空间划分为多个子空间。ZGC仅用0~41位,42~45存元数据(存活信息,和传统gc完全不同),第47~63位固定为0
创建对象时:1)堆空间申请虚拟地址(不映射到真正物理地址),2)ZGC同时为该对象在M0、M1和Remapped分别申请虚拟地址(三个虚拟对应同一物理地址,同一时间只一个有效),3)设置三个,因“空间换时间”,降低GC停顿时间。
[0~4TB) 对应Java堆,[4TB ~ 8TB) 称为M0地址空间,[8TB ~ 12TB) 称为M1地址空间,[12TB ~ 16TB) 预留未使用,[16TB ~ 20TB) 称为Remapped空间。(2)读屏障
是JVM向应用代码插入一小段代码,当应用线程从堆中读对象引用时执行。注意:仅“从堆中读取对象引用”才触发
作用:对象标记和转移时,确定对象引用地址是否满足条件,作出相应动作。
读屏障示例(3)ZGC并发处理演示(gc中地址视图切换过程)
1) 初始化:地址视图设置为Remapped。程序运行,内存中分配对象,满足条件后gc,标记
2) 并发标记阶段:第一次标记为M0,如对象被GC标记或者应用线程访问过,将对象Remapped调为M0(说明对象活跃)
3) 并发转移阶段:再次被设为Remapped。如被GC转移或应用线程访问过,将对象从M0调整为Remapped。ps:区别第二次标记,调为M1
“着色指针和读屏障”用在并发转移、标记阶段,1)传统回收器:对已标记进行一次内存访问,并将对象存活信息放在对象头;2)ZGC:只设置指针地址第42~45位即可,因为是寄存器访问,比访问内存更快
三、ZGC调优实践
1、重要参数
-Xms -Xmx:堆最大/小内存,都设为10G,堆内存保持不变
-XX:ReservedCodeCacheSize -XX:InitialCodeCacheSize: CodeCache的大小, JIT编译的代码都放在CodeCache中,一般服务64m或128m足够。
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC:启用ZGC
-XX:ConcGCThreads:并发回收垃圾的线程。默认总核数12.5%,8核CPU是1。调大GC变快,但占用CPU资源,影响吞吐
-XX:ParallelGCThreads:STW阶段使用线程数,默认总核数60%。
-XX:ZCollectionInterval:ZGC最小时间间隔,秒。
-XX:ZAllocationSpikeTolerance:ZGC触发自适应算法的修正系数,默认2,数值越大,越早的触发ZGC。
-XX:+UnlockDiagnosticVMOptions -XX:-ZProactive:是否启用主动回收,默认开启,这里表示关闭。
-Xlog:设置GC日志中的内容、格式、位置以及每个日志的大小。
2、理解ZGC触发时机
调优第一目标:保证GC完成之前,新对象不会占满堆,导致线程停顿,秒级。触发机制:
1)阻塞内存分配请求触发:来不及回收,将堆占满时,阻塞。日志关键字“Allocation Stall”。
2)基于分配速率的自适应算法(最主要):根据近期对象分配速率及GC时间,计算内存占用阈值,触发GC”。ZAllocationSpikeTolerance控制阈值大小,默认2,越大越早触发。“Allocation Rate”。
3)基于固定时间间隔:ZCollectionInterval控制,适合增流量场景,自适应算法触发可能会过晚,阻塞。“Timer”。
4)主动触发规则:类似3,间隔不固定,ZGC自行算,通过-ZProactive关闭,以免GC频繁,影响服务可用性。“Proactive”。
5)预热规则:服务刚启动,不需关注“Warmup”。
6)外部触发:代码中显式调System.gc()。System.gc()”。
7)元数据分配触发:元数据区不足,不需关注。Metadata GC Threshold
3、理解ZGC日志(完整GC过程)
该日志过滤了进入安全点的信息。正常情况,在一次GC过程中还穿插着进入安全点的操作。
Start:开始GC,GC触发原因。上图是自适应算法。
Phase-Pause Mark Start:初始标记,会STW。
Phase-Pause Mark End:再次标记,会STW。
Phase-Pause Relocate Start:初始转移,会STW。
Heap信息:记录GC中Mark、Relocate前后堆大小变化。High和Low记录最大/小值,如100%,GC一定内存分配不足,更早更快触发GC时机
GC信息统计:定时打印垃圾收集信息,10秒内、10分钟内、10个小时内,启动到现在所有统计信息。排查异常点
4、理解ZGC停顿原因(6种)
1)初始标记:日志中Pause Mark Start。
2)再标记:日志中Pause Mark End。
3)初始转移:日志中Pause Relocate Start。
4)内存分配阻塞:内存不足阻塞"Allocation Stall"。
4)安全点:所有线程进入安全点才能GC,定期进入判断是否要GC。先等后,直到所有挂起
6)dump线程、内存:比如jstack、jmap命令
5、案例一:秒杀流量突增,出现性能毛刺
日志:大量“Allocation Stall”,多出现在“自适应算法,内存分配阻塞
解决方法:1)固定间隔触发-XX:ZCollectionInterval。调为5秒,更短
2)更早触发GC,增大-XX:ZAllocationSpikeTolerance,预测内存分配速率,默认值2设置5,值越大越早触发
6、案例二:压测时,流量逐渐增大一定程度后,出现性能毛刺
日志信息:平均1秒GC/次,两次几乎没间隔,内存分配阻塞
分析:触发及时,但内存标记和回收慢
解决方法:加快并发标记和回收速度,增大-XX:ConcGCThreads默认值核数1/8,8核是1。影响系统吞吐,如GC间隔时间 > GC周期,不建议调整
GC Roots 数量大,单次GC停顿时间长
四、升级ZGC效果
1、延迟降低
低延迟(TP999 < 200ms)场景中收益较大:
TP999(99.9%请求都能被响应的最小耗时):下降12~142ms,下降幅度18%~74%。
TP99:下降5~28ms,下降幅度10%~47%。
超低延迟(TP999 < 20ms)和高延迟(TP999 > 200ms)收益不大,瓶颈不是GC,是外部依赖的性能。
2、吞吐下降
不适合吞吐量优先场景。CMS升级ZGC,吞吐量明显降低。1)ZGC单代,每次处理对象更多,耗CPU;2)读屏障,耗费额外计算资源
总结:性能优秀:全部并发;STW极短<10ms;得益于着色指针和读屏障
https://zhuanlan.zhihu.com/p/170572432
网友评论