说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下( 四 )

  • 元数据分配触发:元数据区不足时导致 , 一般不需要关注 。日志中关键字是“Metadata GC Threshold” 。
  • 理解ZGC日志
    一次完整的GC过程 , 需要注意的点已在图中标出 。
    说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下文章插图
    注意:该日志过滤了进入安全点的信息 。 正常情况 , 在一次GC过程中还穿插着进入安全点的操作 。
    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记录了其中的最大值和最小值 , 我们一般关注High中Used的值 , 如果达到100% , 在GC过程中一定存在内存分配不足的情况 , 需要调整GC的触发时机 , 更早或者更快地进行GC 。
    • GC信息统计:可以定时的打印垃圾收集信息 , 观察10秒内、10分钟内、10个小时内 , 从启动到现在的所有统计信息 。 利用这些统计信息 , 可以排查定位一些异常点 。
    日志中内容较多 , 关键点已用红线标出 , 含义较好理解 , 更详细的解释大家可以自行在网上查阅资料 。
    说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下文章插图
    理解ZGC停顿原因
    我们在实战过程中共发现了6种使程序停顿的场景 , 分别如下:
    • GC时 , 初始标记:日志中Pause Mark Start 。
    • GC时 , 再标记:日志中Pause Mark End 。
    • GC时 , 初始转移:日志中Pause Relocate Start 。
    • 内存分配阻塞:当内存不足时线程会阻塞等待GC完成 , 关键字是"Allocation Stall" 。

    说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下文章插图
    • 安全点:所有线程进入到安全点后才能进行GC , ZGC定期进入安全点判断是否需要GC 。 先进入安全点的线程需要等待后进入安全点的线程直到所有线程挂起 。
    • dump线程、内存:比如jstack、jmap命令 。

    说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下文章插图
    说不定它更好用!新一代垃圾回收器ZGC,带你探索并实践下文章插图
    调优案例我们维护的服务名叫Zeus , 它是美团的规则平台 , 常用于风控场景中的规则管理 。 规则运行是基于开源的表达式执行引擎Aviator 。 Aviator内部将每一条表达式转化成Java的一个类 , 通过调用该类的接口实现表达式逻辑 。
    Zeus服务内的规则数量超过万条 , 且每台机器每天的请求量几百万 。 这些客观条件导致Aviator生成的类和方法会产生很多的ClassLoader和CodeCache , 这些在使用ZGC时都成为过GC的性能瓶颈 。 接下来介绍两类调优案例 。
    内存分配阻塞 , 系统停顿可达到秒级
    案例一:秒杀活动中流量突增 , 出现性能毛刺
    日志信息:对比出现性能毛刺时间点的GC日志和业务日志 , 发现JVM停顿了较长时间 , 且停顿时GC日志中有大量的“Allocation Stall”日志 。
    分析:这种案例多出现在“自适应算法”为主要GC触发机制的场景中 。 ZGC是一款并发的垃圾回收器 , GC线程和应用线程同时活动 , 在GC过程中 , 还会产生新的对象 。 GC完成之前 , 新产生的对象将堆占满 , 那么应用线程可能因为申请内存失败而导致线程阻塞 。 当秒杀活动开始 , 大量请求打入系统 , 但自适应算法计算的GC触发间隔较长 , 导致GC触发不及时 , 引起了内存分配阻塞 , 导致停顿 。
    解决方法:
    (1)开启”基于固定时间间隔“的GC触发机制:-XX:ZCollectionInterval 。 比如调整为5秒 , 甚至更短 。 (2)增大修正系数-XX:ZAllocationSpikeTolerance , 更早触发GC 。 ZGC采用正态分布模型预测内存分配速率 , 模型修正系数ZAllocationSpikeTolerance默认值为2 , 值越大 , 越早的触发GC , Zeus中所有集群设置的是5 。
    案例二:压测时 , 流量逐渐增大到一定程度后 , 出现性能毛刺
    日志信息:平均1秒GC一次 , 两次GC之间几乎没有间隔 。
    分析:GC触发及时 , 但内存标记和回收速度过慢 , 引起内存分配阻塞 , 导致停顿 。
    解决方法:增大-XX:ConcGCThreads ,加快并发标记和回收速度 。 ConcGCThreads默认值是核数的1/8 , 8核机器 , 默认值是1 。 该参数影响系统吞吐 , 如果GC间隔时间大于GC周期 , 不建议调整该参数 。