天天看点

一次JVM内存溢出问题解决记录

问题解决有一段时间了,当时比较忙,没有记录下来,最近闲下来了,就打算记录一下,问题的解决过程,毕竟以前看过虚拟机相关的知识,但由于公司项目都是内部项目,对性能要求不太高,很少用到虚拟机知识进行优化之类的,用虚拟机知识解决问题的机会也不多。

先说下背景,这是一个电商相关的项目,已经上线运行了有段时间了,没用maven管理依赖,用到的jar包直接放在了工程里,Spring+struts+hibernate实现的,本身提供了电商页面管理的功能,通过页面,可以管理商品,管理活动,然后通过freemarker生成静态页面,上传服务器,原来是ftp上传服务器,和这个项目在同一个服务器上,生成的静态页面是分为不同电商终端访问的,比如有微信端,有app端等等,由于静态文件生成后上传ftp与该项目在同一服务器上,后来性能感觉不太够用了,就想把静态文件分离出来,上传到OSS上,这个任务当时由我来做的,由于用到OSS,所以jdk由原来的jdk7升级为jdk8,tomcat也由tomcat7升为tomcat8。

问题的发生是这样的,部署到生产环境后,每十个小时左右服务会挂一次,看日志是堆内存溢出了,不过回忆了下当时理出来的业务,还有我们当时做过的改动,想不出哪里会导致溢出的,所以就到线上把JVM内存dump出来转储到文件里,然后把文件下下来到本地,用工具打开分析,用的是eclipse memory analyzer来分析(第一次用,之前就用的jdk自带的工具看过那么两次dump出来的堆文件),这个工具感觉还是挺好用的,直接就看到了占内存最大的是哪个对象,是一个Blocking队列,里面存的对象是LoggingEvent对象,看着像是和日志相关的类,查了下,还确实是日志相关包里的类,工程代码里应该没有用到才对,只好在本地启动工程,然后在队列的添加对象方法中加断点,然后就发现很快进到了断点,看了下队列的size,运行没多少时间就已经有几十万对象了,确实很大,看了下断点处的调用栈,感觉和业务代码没有太大关系,都是Spring框架代码,然后一直在日志相关包中来回调用,最后就调到了加入队列的代码。

然后和另外两个同事讨论了一翻,最后猜测,可能是日志门面slf4j获取到的日志信息没有最终打到文件,而是直接保存到了内存里,导致队列一直增加,最终内存溢出,而原来这些日志依赖并没有问题,应该是和jdk还有tomcat的升级有关的,所以我先把项目里所有日志相关的包删除了,再找了个运行环境也是dk8,tomcat8的项目打的war包,把里面的所有日志包拷出来放到项目里,在本地启动,发现问题已经顺利解决。