Why: 首先怀疑是JVM内存配置有问题,因此让客户检查了JVM内存的配置。之前也出现过客户年轻代配置过小,导致很多对象都跑到老年代里面的情况。
Answer: 查看客户配置内存为-Xms:2048M -Xmx:4096 -Xmn:1512m,年轻代和老年代的比例符合1:2的要求,因此排除了这个原因。
Why: 接下来只能按照传统的方案导出dump文件进行分析,提供了客户两个命令:
# 导出dump文件
jmap -dump:format=b,file=/home/admin/logs/heap.hprof 6214
# 执行一次full gc后导出dump文件
jmap -dump:format=live,b,file=/home/admin/logs/heap.hprof 6214
Answer: 导出两个文件后,发现第一个文件有2G大,但是第二个文件只有400M。因为环境限制就只能让客户把第二个小一点的文件拿下来分析。做这一步主要是用来判断服务是否发生内存泄漏,即存在对象不能释放。文件拿下分析之后发现并没有存在特殊的对象。
Why: 通过jprofiler分析了该文件并未找到有内存泄漏后,只能通过jstat去查看内存增长的过程中有没有异常。
# 间隔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后,发现内存果然明显降低了,老年代内存被释放掉了。
# 调用java.lang.System.gc()
jcmd [pid] GC.run
因为gc机制跟垃圾收集器有关系,所以需要查看服务使用到的垃圾收集器。命令如下:
jmap -heap 29871
通过上述命令看到我们项目使用的正式Java8默认是垃圾收集器,Parallel Scavenge垃圾收集器管理的新生代,ParOldGen表示由Parallel Old管理的老年代。然后就开始搜索Parallel Old相关的配置,网上文章较少,只能从实体书入手,经过阅读《Java虚拟器》后了解到该垃圾收集器并没有限制内存使用率达到多少就必须做full gc,只有CMS垃圾收集器才有这个限制。