👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事中“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
《精进 Java 学习指南》:系统学习,互联网主流技术栈
《必读 Java 源码专栏》:知其然,知其所以然
👉这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:
Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本
来源:heapdump.cn/article/1859160
通过这一个多月的努力,将 FullGC 从 40 次/天优化到近 10 天才触发一次,而且 YoungGC 的时间也减少了一半以上,这么大的优化,有必要记录一下中间的调优过程。
对于 JVM 垃圾回收,之前一直都是处于理论阶段,就知道新生代,老年代的晋升关系,这些知识仅够应付面试使用的。
问题
前一段时间,线上服务器的 FullGC 非常频繁,平均一天 40 多次,而且隔几天就有服务器自动重启了,这表明服务器的状态已经非常不正常了,得到这么好的机会,当然要主动请求进行调优了。
未调优前的服务器 GC 数据,FullGC 非常频繁。
![483d510ebe66f16e2afecc91c6177758.png](https://img-blog.csdnimg.cn/img_convert/483d510ebe66f16e2afecc91c6177758.png)
首先服务器的配置非常一般(2 核 4G),总共 4 台服务器集群。每台服务器的 FullGC 次数和时间基本差不多。其中 JVM 几个核心的启动参数为:
-Xms1000M -Xmx1800M -Xmn350M -Xss300K -XX:+DisableExplicitGC -XX:SurvivorRatio=4 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
-Xmx1800M:设置 JVM 最大可用内存为 1800M。
-Xms1000m:设置 JVM 初始化内存为 1000m。此值可以设置与 -Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存。
-Xmn350M:设置年轻代大小为 350M。整个 JVM 内存大小 = 年轻代大小 + 年老代大小。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8。
-Xss300K:设置每个线程的堆栈大小。JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
第一次优化
一看参数,马上觉得新生代为什么这么小,这么小的话怎么提高吞吐量,而且会导致 YoungGC 的频繁触发,如上图的新生代收集就耗时 830s。
初始化堆内存没有和最大堆内存一致,查阅了各种资料都是推荐这两个值设置一样的,可以防止在每次 GC 后进行内存重新分配。
基于前面的知识,于是进行了第一次的线上调优:提升新生代大小,将初始化堆内存设置为最大内存。
-Xmn350M -> -Xmn800M
-XX:SurvivorRatio=4 -> -XX:SurvivorRatio=8
-Xms1000m ->-Xms1800m
将 SurvivorRatio 修改为 8 的本意是想让垃圾在新生代时尽可能的多被回收掉。
就这样将配置部署到线上两台服务器(prod,prod2 另外两台不变方便对比)上后,运行了 5 天后,观察 GC 结果,YoungGC 减少了一半以上的次数,时间减少了 400s,但是 FullGC 的平均次数增加了 41 次。YoungGC 基本符合预期设想,但是这个 FullGC 就完全不行了。
![087e8bcf7d4304506525e65387156884.png](https://img-blog.csdnimg.cn/img_convert/087e8bcf7d4304506525e65387156884.png)
就这样第一次优化宣告失败。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
第二次优化
在优化的过程中,我们的主管发现了有个对象 T 在内存中有一万多个实例,而且这些实例占据了将近 20M 的内存。于是根据这个 bean 对象的使用,在项目中找到了原因:匿名内部类引用导致的,伪代码如下:
public void doSmthing(T t){
redis.addListener(new Listener(){
public void onTimeout(){
if(t.success()){
//执行操作
}
}
});
}
由于 listener 在回调后不会进行释放,而且回调是个超时的操作,当某个事件超过了设定的时间(1 分钟)后才会进行回调,这样就导致了 T 这个对象始终无法回收,所以内存中会存在这么多对象实例。
通过上述的例子发现了存在内存泄漏后,首先对程序中的 error log 文件进行排查,首先先解决掉所有的 error 事件。然后再次发布后,GC 操作还是基本不变,虽然解决了一点内存泄漏问题,但是可以说明没有解决根本原因,服务器还是继续莫名的重启。
内存泄漏调查
经过了第一次的调优后发现内存泄漏的问题,于是大家都开始将进行内存泄漏的调查,首先排查代码,不过这种效率是蛮低的,基本没发现问题。于是在线上不是很繁忙的时候继续进行 dump 内存,终于抓到了一个大对象。
![86ff25b0ab14a469acef0217bc19fdad.png](https://img-blog.csdnimg.cn/img_convert/86ff25b0ab14a469acef0217bc19fdad.png)
![4ca1e32fa130c54e5dbc0af608f5e0cb.png](https://img-blog.csdnimg.cn/img_convert/4ca1e32fa130c54e5dbc0af608f5e0cb.png)
这个对象竟然有 4W 多个,而且都是清一色的 ByteArrowRow 对象,可以确认这些数据是数据库查询或者插入时产生的了。
于是又进行一轮代码分析,在代码分析的过程中,通过运维的同事发现了在一天的某个时候入口流量翻了好几倍,竟然高达 83MB/s ,经过一番确认,目前完全没有这么大的业务量,而且也不存在文件上传的功能。
咨询了阿里云客服也说明完全是正常的流量,可以排除攻击的可能。
![326078caf491b04700d9ecea52e0c7ae.png](https://img-blog.csdnimg.cn/img_convert/326078caf491b04700d9ecea52e0c7ae.png)
就在我还在调查入口流量的问题时,另外一个同事找到了根本的原因,原来是在某个条件下,会查询表中所有未处理的指定数据,但是由于查询的时候 where 条件中少加了模块这个条件,导致查询出的数量达 40 多万条,而且通过 log 查看当时的请求和数据,可以判断这个逻辑确实是已经执行了的,dump 出的内存中只有 4W 多个对象,这个是因为 dump 时候刚好查询出了这么多个,剩下的还在传输中导致的。而且这也能非常好的解释了为什么服务器会自动重启的原因。
解决了这个问题后,线上服务器运行完全正常了,使用未调优前的参数,运行了 3 天左右 FullGC 只有 5 次。
![fdbc7fc2c2ba86d70c7eb9dff33ade53.png](https://img-blog.csdnimg.cn/img_convert/fdbc7fc2c2ba86d70c7eb9dff33ade53.png)
第二次调优
内存泄漏的问题已经解决了,剩下的就可以继续调优了,经过查看 GC log,发现前三次 FullGC 时,老年代占据的内存还不足 30%,却发生了 FullGC。
于是进行各种资料的调查,在 https://blog.csdn.net/zjwstz/article/details/77478054
博客中非常清晰明了的说明 metaspace 导致 FullGC 的情况,服务器默认的 metaspace 是 21M,在GC log
中看到了最大的时候 metaspace 占据了 200M 左右,于是进行如下调优,以下分别为 prod1 和 prod2 的修改参数,prod3,prod4 保持不变。
-Xmn350M -> -Xmn800M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75
和
-Xmn350M -> -Xmn600M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75
prod1 和 2 只是新生代大小不一样而已,其他的都一致。到线上运行了 10 天左右,进行对比:
prod1:
![7a4981a9c514f403e27e53b604073dd6.png](https://img-blog.csdnimg.cn/img_convert/7a4981a9c514f403e27e53b604073dd6.png)
prod2:
![8f73bdbb9b99e89ba9e9c29964bf1f54.png](https://img-blog.csdnimg.cn/img_convert/8f73bdbb9b99e89ba9e9c29964bf1f54.png)
prod3:
![a6be9dc59c393c82f1b4b35e52672870.png](https://img-blog.csdnimg.cn/img_convert/a6be9dc59c393c82f1b4b35e52672870.png)
prod4:
![40cffaf3f9cc8a1c6544ef0461ecec69.png](https://img-blog.csdnimg.cn/img_convert/40cffaf3f9cc8a1c6544ef0461ecec69.png)
对比来说,1,2 两台服务器 FullGC 远远低于 3,4 两台,而且 1,2 两台服务器的 YounGC 对比 3,4 也减少了一半左右。
而且第一台服务器效率更为明显,除了 YoungGC 次数减少,而且吞吐量比多运行了一天的 3,4 两台的都要多(通过线程启动数量),说明 prod1 的吞吐量提升尤为明显。
通过 GC 的次数和 GC 的时间,本次优化宣告成功,且 prod1 的配置更优,极大提升了服务器的吞吐量和降低了 GC 一半以上的时间。
prod1 中的唯一一次 FullGC:
![391874f193ecd30883034387f06261f1.png](https://img-blog.csdnimg.cn/img_convert/391874f193ecd30883034387f06261f1.png)
![385e97c94daa02f0544ebab31303b313.png](https://img-blog.csdnimg.cn/img_convert/385e97c94daa02f0544ebab31303b313.png)
通过 GC log 上也没看出原因,老年代在 cms remark 的时候只占据了 660M 左右,这个应该还不到触发 FullGC 的条件,而且通过前几次的 YoungGC 调查,也排除了晋升了大内存对象的可能,通过 metaspace 的大小,也没有达到 GC 的条件。这个还需要继续调查,有知道的欢迎指出下,这里先行谢过了。
总结
通过这一个多月的调优总结出以下几点:
FullGC 一天超过一次肯定就不正常了。
发现 FullGC 频繁的时候优先调查内存泄漏问题。
内存泄漏解决后,jvm 可以调优的空间就比较少了,作为学习还可以,否则不要投入太多的时间。
如果发现 CPU 持续偏高,排除代码问题后可以找运维咨询下阿里云客服,这次调查过程中就发现 CPU 100% 是由于服务器问题导致的,进行服务器迁移后就正常了。
数据查询的时候也是算作服务器的入口流量的,如果访问业务没有这么大量,而且没有攻击的问题的话可以往数据库方面调查。
有必要时常关注服务器的 GC,可以及早发现问题。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)