Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【转载】G1GC垃圾回收器实践案例

2023-10-28 2368点热度 0人点赞 0条评论

考虑CMS无法有效避免FGC,且单次GC耗时经常不可控。因此在如下两种场景下倾向于使用G1替换CMS:

  1. 大堆系统长时间FGC会引起上层服务异常,比如RegionServer/HiveServer等。
  2. 对读写毛刺比较敏感的在线数据库服务,比如在线推荐场景下的HBase,GC耗时过长就会导致整体可用率降低。

笔者在2019年开始将集团内部多数HBase所用CMS升级到G1,升级后大部分集群的GC性能提升还是比较显著的,接下来分为三个小节分别介绍相关的实践经验。

GC性能优化表现

下面是两个经过简单参数调整后就立刻见到优化效果的案例:
  1. A集群调优后GC耗时由调整前的100ms左右降低到20ms左右。如下图所示:
图片
  1. B集群调优后GC耗时由调整前的100ms降低到15ms左右,对应客户端写入RT由调整前的13ms降低到4ms。
图片图片

G1设置不好也会发生FGC

生产线上一个RegionServer改成G1GC运行一段时间后发生了FGC,导致节点宕机,服务受到一定影响。查看了对应的GC日志,如下图所示:

图片
从日志中可以看到这次GC没有GC出来任何空间,此时还需要为对象申请空间,发现内存已经用满了无法继续分配,最终导致Full GC。
经过进一步确认,发现MixGC触发的时机不对,进程设置的最大内存为40g,InitiatingHeapOccupancyPercent值为65,理论上所占内存达到40g*65%=26g的时候就应该执行MixGC。但实际上JVM执行MixGC的时候内存已经占用到了34g。如下图:
图片
为什么会这样?实际上是因为RegionServer在运行过程中就需要这么多内存。这个根据Memstore的总大小就可以确认:
图片
在高峰期光Memstore就需要25G的内存,已经达到了InitiatingHeapOccupancyPercent阈值所规定的的MixGC执行阈值,这还不算读缓存以及预留的10%~20%内存。可见,JVM就是需要36G那么多内存,GC不下去属于正常情况,一旦在GC的时候进来大量数据,就很有可能导致所有内存都被耗尽,最终FGC。
为什么Memstore占用那么多内存?是因为这个RegionServer上的region很多,达到了559个,一个region最大占用128M内存,25G内存也是容易达到的。
这个案例说明了在堆大小设置不合理的情况下是会发生这种FGC的。那应该如何有效避免呢?可以做如下两点优化:
1. 内存需要增大,从40g增大到60g。
2. 将参数hbase.regionserver.global.memstore.size需要从0.65稍微调小。、
对于RegionServer来说,堆内存大小需要结合Memstore以及Blockcache总大小进行设置,而不是拍脑袋。

G1参数调优实战

生产线上另一个HBase集群从CMS GC改为G1GC后,一两天之后总发现在整点的时候会出现GC延迟的毛刺,如下图所示:
图片
观察对应RegionServer节点的吞吐量,确认有业务会在整点有一个批量写入操作,如下图所示:
图片
因此基本上可以确定是因为这个业务在整点的大吞吐量写入(单条记录字段很大)导致GC有个毛刺的。这个GC毛刺会导致业务写入延迟有所增大,如下所示:
图片
DBA担心如果所有节点整点同时抖动,会不会对整个集群产生很大的影响,这个担心是合理的。需要说明的是,与G1GC对比,CMS GC在整点的时候并没有明显的抖动,所以G1GC肯定在这方面还有需要优化的点。
查看G1GC的gc日志,发现一个蹊跷的现象,每次在整点的一轮MixGC的最后一次都会出现一个长时间的GC卡顿,如下图所示:
图片
看着日志,这次长时间的GC卡顿主要是Scan RS这个阶段比较长,2.1s中有1.99花费在Scan RS。那这个Scan RS是干什么的呢?
什么是RS(Remembered Set)?为什么Scan RS会很长?
这个内容在之前的系列文章中详细分析过,但为了文章的完整性,不麻烦读者再返回去找对应文章,这里再简单介绍一下。
G1GC模式下Java堆内存会分成一个一个的小Region(和HBase中Region不是一个概念),每个Region都会有一个对应的Remembered Set,这个Set主要记录那些引用了这个Region内对象的其他对象。为什么需要记录这些对象呢?是因为G1GC的GC属于整理式GC算法,即每次GC会将一个Region中活的对象拷贝到另一个空闲Region,并清空这个Region,这样就可以防止内存碎片的产生。很显然,对象在GC的过程中会发生移动,大家想想,如果对象移动的话,原先指向这些对象的引用是不是都需要更新,引用地址从原地址更新为新地址。为了方便查找那些需要更新地址的引用,就在Remembered Set中记录了那些指向本Region内对象的引用对象。说白了就是,哪个对象指向了这个Region的对象,就要在这个Region对应的Remembered Set中把你记下来,方便后续对你进行更新。上图中Scan RS阶段就是GC过程中扫描哪些指向了本Region中存活对象的引用对象。
Remembered Set具体是什么样的数据结构呢?
要解释这个问题,需要对上述的表述进行更细节的一些补充。首先来看Region这个概念,在实际实现中,每个Region会划分为很多小块,每个小块称为一个Card,每个Card的大小一般为512byte。但是Remembered Set中并不会记录哪个对象引用了对应Region中的对象,这个粒度太细,有可能导致Remembered Set很大,占用太多内存空间。在实际实现中,Remembered Set中记录的是哪个Region上的哪个Card上的对象引用了我这个Region中的对象,只是精确到Card粒度。
按照上述介绍,很显然,如果引用该Region中对象的其他Region(称为引用Region)越多,引用Region中引用Card越多,Remembered Set占用内存空间就会越大。为了控制Remebered Set占用内存空间的大小(毕竟这部分内存空间不是为业务服务的,不应该占用太多),Remembered Set根据引用Region个数的多少,设置了3种不同的实现方式,分别称为:
  • sparse per-region-table (PRT),使用HashMap方式记录引用关系,其中Map的Key是引用Region,Value是一个List,List中存储引用Region中的引用Card列表。
  • fine-grained PRT,还是使用HashMap方式记录引用关系,其中Map的Key是引用Region,但Value不再是List,而是一个bitmap,bit位为1表示对应的Card是引用Card,否则不是引用Card。
  • coarse-grained bitmap,从字面意思可以看出来这就是一个bitmap,不过bitmap中每个bit位引用粒度不再是Card,而是Region。如果bit位值为1,表示这个Region是引用Region,即这个Region中有对象引用了我这个Region中的对象。
上述3种实现方式中,spase PRT和fine-grained PRT都是精确到Card,而coarse-grained bitmap是精确到Region。
这里大家肯定有一个疑问,为什么Remembered Set会设计这3种实现方式呢?什么场景选择哪种实现方式?
Remembered Set实际使用哪种实现方式取决于它对应的Region有多热,就是说如果很多Region中的很多对象都引用这个Region中的对象,那这个Region就是很热,反之如果没有几个外部Region的对象引用这个Region中的对象,这个Region就冷一些。Region热度越高,如果采用fine-grained PRT的话,Remembered Set需要占用的内存空间就会越大,但Remembered Set是JVM自身运作的一个数据结构,不应该占用太多内存空间,不然就会挤占太多本应留给业务的内存空间,所以一旦Region热到一定程度,fine-grained PRT就会退化成coarse-grained bitmap,后者因为粒度更粗,占用内存会更小。但是,coarse-grained bitmap有个非常大的问题就是存储的粒度太粗,会导致扫描Remembered Set反向查询引用对象的时候,需要遍历引用Region查询引用对象,这个时间会很长。这就是Scan RS花费时间长的本质原因。
那有个问题就是fine-grained PRT退化为coarse-grained bitmap的触发条件具体是什么?
根据JVM源码分析,fine-grained PRT退化为coarse-grained bitmap主要取决于一个JVM参数-XX:G1RSetRegionEntries。源码中对这个参数的解释是:Max number of regions for which we keep bitmaps,我的理解是一旦引用Region的数量超过这个阈值,fine-grained PRT就会退化为coarse-grained bitmap。
-XX:G1RSetRegionEntries默认值是多少呢?
官方给的默认值是0,表示会根据当前其他配置进行自适应。根据源码分析,G1RSetRegionEntries默认值根据如下公式计算:
int region_size_log_mb = log(regionsize) - 20;
G1RSetRegionEntries = G1RSetRegionEntriesBase * (region_size_log_mb + 1);

 

其中regionsize表示Region的大小,由参数-XX:G1HeapRegionSize确定,HBase集群配置的是32M。G1RSetRegionEntriesBase是一个固定值,为256。
因此在32M大小Region的场景下,G1RSetRegionEntries 默认值为256 * (log(32 * 1024 * 1024) - 20 + 1) = 1536。
问题解决方案
经过上述分析,我们基本可以推导出如下逻辑:
1. 业务整点写入会导致某些Region成为热Region。
2. 热Region对应的Remembered Set从fine-grained PRT退化为coarse-grained bitmap。
3. coarse-grained bitmap实现方式因为存储的是Region粒度,Scan RS阶段需要扫描对应Region中所有对象来确定是否引用了本Region中的对象,所需时间就会非常长。
4. G1GC在整点的时候Mix GC就会比较长。
5. 业务在整点的时候写入延迟就会有一个抖动。
按照这样的逻辑,那只需要牺牲一些内存空间,使得fine-grained PRT不要退化为coarse-grained bitmap就可以解决这个问题。所以理论上将-XX:G1RSetRegionEntrie从默认的1536增大就可以解决这个问题。目前线上将这个参数从默认值改为了4096,观察一周后,对应的GC延迟对比和业务写入延迟都得到了极大的改善,整点GC抖动不再出现,业务写入延迟也没有抖动。分别如下面两图所示:
图片
图片

文章最后

上一篇文章系统介绍了G1GC的理论知识,本篇文章在此基础上介绍生产线上G1GC的相关实践案例。总结下来:

  1.  使用G1GC替换CMS GC可以优化GC性能。
  2.  一旦G1GC参数配置不合理,依然会触发FGC。
  3.  G1GC日志相比CMS GC来说非常详细,可以有效结合日志进行相关问题分析以及性能优化。

 

本文仅供学习!所有权归属原作者。侵删!文章来源: 大数据基建 -子和 :http://mp.weixin.qq.com/s/b7rpPwAsbZmIyNmVWD0alQ

更多文章:

  1. 一文看懂G1GC垃圾回收器
  2. 【视频】如何写高效内存Java代码——How to Write Memory-Efficient Java Code
  3. JVM GC问题定位排查方法综述
  4. 一文看懂”ParNew+CMS”组合垃圾回收器
  5. ParNew+CMS 实践案例 (一)- NameNode YGC诊断优化
  6. 生产环境的CMS垃圾回收,一定要这样配置参数
  7. ParNew+CMS 实践案例 : HiveMetastore FullGC诊断优化
  8. JVM GC配置指南
  9. JVM 内存大对象监控和优化实践
  10. 高并发场景下JVM调优实践之路
标签: 转载 JVM GC Java 垃圾回收器 性能调优 FGC G1GC
最后更新:2023-10-28

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
    • GC性能优化表现
    • G1设置不好也会发生FGC
    • G1参数调优实战
    • 文章最后
最新 热点 推荐
最新 热点 推荐
干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink? 如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?视频笔记:什么是AI 智能体?
事件驱动架构(EDA) VS 请求响应架构(RR) Routing Elasticsearch架构VI:路由 系统设计 | 业务编号生成 企业4A架构:业务、应用、数据、技术的完美融合 LangChain:打造自己的LLM应用 应用分层架构最佳实践:Alibaba COLA 4.0 Eureka源码剖析之五:服务下线 既生@Resource,何生@Autowired?

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (2) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (18) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x