谈谈项目中主动 full gc 的一些问题
背景
前一段时间在公司一个技术群里,有人在问“有人在线上使用 32G 内存的服务”。我司线上内存标准配置都是 8G 的。我就问了一下使用 32G 内存碰到了啥问题。他的关注点在于一次 full gc 时间的长短上。他们面临的问题是上游设置的超时时间太短,一旦他们服务发生 full gc,上游服务必然超时。其实很多项目都会面临这个问题。我自己的解决方案就是主动 full gc。并且在群里给出了相关的代码。没想到大家对这个主动 full gc 都很感兴趣,这几天不少人咨询了我这方面的事情。所以就写一篇文章来说说主动 full gc。
主动 full gc 的标准流程
主动 full gc 的流程应该如下:
检查是否快满足 full gc 条件,若到达临界点,进入 2;
主动把服务给注册下线;
调用 System.gc();
主动把服务注册上线;
如何检查 old 区域中的使用率呢?
SunJVM 提供了一组内存检测接口 MemoryPoolMXBean,可以在代码中直接调用这个接口检测各个内存使用情况,具体使用方式可以参考cat中的 MemoryInformations。
如何检查是否到达临界点?
一般情况下我们只需要关注 old 区域即可(有些应用会生成临时类除外)。在使用 CMS 作为垃圾回收器的时候,我们只要检查 old 区域的使用率是否快要到达 CMSInitiatingOccupancyFraction(并且加上参数 UseCMSInitiatingOccupancyOnly,不要让 JVM 替我们垃圾收集决策)即可。
如何很好的把服务注册下线?
每个公司的基础组件可能都不同,但是一般情况下都会提供这样的功能。比如我司直接在代码中设置状态即可(configStatus.setRuntimeStatus(CustomizedStatus.DEAD);),一行代码即可。但是一定要注意从发起下线到真正下线,中间肯定有时间延迟;而且发起下线的时候服务本身还有一些请求需要处理完成才可以;所以发起线下之后,必须过一段时间才能执行主动 GC。
如何很好的执行 System.gc()?
你是不是认为这个很简单啊,直接调用 System.gc()即可?JDK 中代码中有需要直接调用 System.gc(),那就看看别人别人怎么写的吧,可以参考 DirectByteBuffer 中的写法:
大家可以思考一下为什么要在调用 System.gc()之后暂时一段时间。
还有要使 System.gc()这个生效,必须在 jvm 中去掉 DisableExplicitGC 这个参数。
如何优化主动 full gc
控制同时主动 full gc 机器数
主动 full gc 中最重要的就是要防止所有机器同时主动 full gc,一旦所有机器同时 full gc 就可能造成上游服务感知不到服务在线了。如果项目中在这方面有比较严的要求,可以使用分布式信号量来保证同时只有特定数目的机器发生 full gc;若不需要那么严格,可以让某些机器只在时间末尾为 N 的发生主动 full gc。也就是说可以通过时间末尾来控制最多有 1/N 台机器主动发生 full gc。
降低主动 full gc 的频率
假设一次主动 full gc 的的时间分为服务上下线时间 T1 和 System.gc()执行时间 T2。一般情况下 T1>>>T2,T2 一般几百 ms 肯定就可以完成了;T1 比较固定,基本上需要几秒甚至十几秒。 这样的话,如果主动降级 full gc 频率,那么 T1 的次数很明显下降很多;就算每次主动 full gc T2 时间上升了对整体停服时间也会降低很多。。
那么调大 old 区域的大小肯定可以降低 full gc 频率。那能不能提高 old 的区域的使用率呢,也就是提高 CMSInitiatingOccupancyFraction 的配置值。我们知道对于 CMS 有内存碎片化问题,所以这个参数一般配置的不是很大,比如默认的配置是 68%。但是调用 System.gc()的时候,执行的不是 CMS GC,而是 full gc,不存在内存碎片化问题。所以可以通过提高 CMSInitiatingOccupancyFraction 参数来降低 full gc 频率。
如何设置好主动 full gc 的临界点
首先、这个临界点肯定要低于 CMSInitiatingOccupancyFraction 的配置,这样主动 full gc 才会生效。这个要结合 full gc 的频率以及主动 full gc 时的停服时间还有允许同时主动 full gc 的机器数目。一般的应用来说我们只要保证 cms gc 来临的前 5 分钟有主动 full gc 的机会就可以了。如果每分钟一次机会,那么 4 次机会都没有轮上主动 full gc,那么我认为要么 full gc 太频繁了要么 full gc 时间都太集中了,不管哪种都应该需要优化了。观察一下 cms gc 前 5 分钟的 old 区域使用率设置为临界点即可。
主动 full gc 的代码 demo
总结
主动 full gc 还是有不少学问的,比如 System.gc()前后的 sleep、如何防止所有机器同时发生主动 full gc、如何优化主动 full gc 频率等。
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注公众号 『 java 烂猪皮 』,不定期分享原创知识。
同时可以期待后续文章 ing🚀
作者:朱纪兵
评论