背景
- 我们的产品旨在为金融客户提供全面的解决方案服务,包括资金管理、交易处理等。我们的团队拥有丰富的行业经验和技术实力,能够根据客户的需求定制个性化的解决方案,并提供快速高效的实施和售后服务。 为了确保客户在使用我们的产品时能够获得最佳的体验和服务,公司提供了完善的监控服务。该监控平台可以实时监测产品的运行状态、性能指标,并及时发现和解决问题,保障客户的业务连续性和数据安全。
问题现象
- 客户反馈在生产过程中出现了"内存使用率过高"的告警。经过检查,我们发现是JVM内存使用率超过了80%。
问题排查过程
-
Why: 首先怀疑是JVM内存配置有问题,因此让客户检查了JVM内存的配置。之前也出现过客户年轻代配置过小,导致很多对象都跑到老年代里面的情况。 Answer: 查看客户配置内存为-Xms:2048M -Xmx:4096 -Xmn:1512m,年轻代和老年代的比例符合1:2的要求,因此排除了这个原因。
-
Why: 接下来只能按照传统的方案导出dump文件进行分析,提供了客户两个命令:
bash# 导出dump文件 jmap -dump:format=b,file=/home/admin/logs/heap.hprof 6214
bash# 执行一次full gc后导出dump文件 jmap -dump:format=live,b,file=/home/admin/logs/heap.hprof 6214
Answer: 导出两个文件后,发现第一个文件有2G大,但是第二个文件只有400M。因为环境限制就只能让客户把第二个小一点的文件拿下来分析。做这一步主要是用来判断服务是否发生内存泄漏,即存在对象不能释放。文件拿下分析之后发现并没有存在特殊的对象。
-
Why: 通过jprofiler分析了该文件并未找到有内存泄漏后,只能通过jstat去查看内存增长的过程中有没有异常。
bash# 间隔5秒钟打印20次服务垃圾回收情况 jstat -gc 1212 5000 20
Answer: 通过上述命令,因为监控的原因所以年轻代end区一直有对象创建,然后等到end区满了之后就触发了一次 Minor GC ,存活的对象晋升到了Servivor区;持续这个过程等到一次Minor GC时 Servivor区也满了时,能观察到Old区内存使用量有增长;然后直到Old区内存使用率超过80%仍然未触发full gc释放内存。
-
Why: 此时引申出一个问题,什么情况下会触发full gc? Answer: 通过以下命令主动触发一次full gc后,发现内存果然明显降低了,老年代内存被释放掉了。
bash# 调用java.lang.System.gc() jcmd [pid] GC.run
因为gc机制跟垃圾收集器有关系,所以需要查看服务使用到的垃圾收集器。命令如下:
bashjmap -heap 29871
通过上述命令看到我们项目使用的正式Java8默认是垃圾收集器,Parallel Scavenge垃圾收集器管理的新生代,ParOldGen表示由Parallel Old管理的老年代。然后就开始搜索Parallel Old相关的配置,网上文章较少,只能从实体书入手,经过阅读《Java虚拟器》后了解到该垃圾收集器并没有限制内存使用率达到多少就必须做full gc,只有CMS垃圾收集器才有这个限制。
问题分析
- 经过上述排查过程,可以明显看出是监控项设置不合理所导致的问题。建议客户将对应限制调高或关闭以解决这个问题。另外,我们也可以考虑使用其他垃圾收集器来优化性能。
问题总结
- 经过一整天的排查,我们终于找到了问题所在。然而,这次排查让我意识到了自己在JVM相关知识方面的不足。虽然之前也曾经多次排查过JVM相关的问题,但是一直没有形成完整的知识体系,导致每次都需要重新学习和查找资料。因此,我决定要梳理出一个完整的知识树,以便更好地解决今后工作中遇到的各种问题。