问题描述
最近遇到内存泄漏的问题:在阿里服务器上部署了一个定时爬虫,用 springboot 写的项目;使用 webmagic 爬虫框架,最终数据写入 mysql 并且添加 elasticsearch 索引;当跑到一个月的时候服务出现“假死”,表现是内存用光了,程序一直在 full gc , 抛出 OOM (overhead limit exceed)。
附上一天的 gcviewer 图:
图1-1 gc 最近日志 图1-2 gc 20小时前日志 图1-3 程序一直full gc从上面的 gc 日志得出:老年代小幅度只增不减,存在内存泄漏。为了方便定位到问题,使用内存分析工具而不去检查源码。
实验准备
计划
我将一样的程序在测试环境运行,记录 gc 日志以及 dump 转储文件,通过工具定位内存泄漏。
工具
MAT 一款功能强大的 java 堆内存分析器,用于查找内存泄漏以及内存消耗情况。
概念1: 浅堆(shallow heap)和深堆(retained heap)
浅堆只与对象结构有关,如图4所示,在32位系统中 int 4 字节,对象引用4字节,对象头8字节;那么 String 对象一共是3*4 + 4 + 8 = 24字节,这就是他的浅堆大小,不管char[] 有多少个字符始终是24字节。
图2.2-1 String 结构深堆是指对象A的保留集(通过对象A直接或者间接访问到的所有对象)中所有对象的浅堆大小之和,即对象A被释放的所有对象(包括自己)。
概念2:支配树(dominator tree)
图2.2-2 左边是引用关系,右边是支配树在 MAT 中提高称为支配树的对象图,体现对象实例间的支配关系。 如果引用对象A必经过对象B,则认为对象B为对象A的直接支配者。
有如下性质:对象 A 的子树表示对象A的保留集;对象A支配对象B,则对象A的直接支配者也支配对象B;支配树的边与对象引用图的边不直接对应。
如图5,对象A和B的直接引用是根对象,则A和B被根对象直接支配;对象C经过A引用,也可经过B引用,则由根对象支配; F和D相互引用,但是引用F需要经过D,而引用D不一定经过F,则D直接支配F
概念3:垃圾回收根(MAT javaBasic->GC Roots 可直接展示)
实际分析:
内存泄漏存在以下特点:老年代越来越大,某些类内存占比大,被垃圾回收根引用,存在大对象。
程序跑了3天,得到内存转储文件;查看 MAT 内存泄漏分析报告:
图3.1-1 内存泄漏报告查看 QueryPlanCache 中缓存含有 in 的 sql 语句:
图3.1-2 in-sql语句解决方案参见:https://stackoverflow.com/questions/31557076/spring-hibernate-query-plan-cache-memory-usage
参考资料
《Java 程序性能优化》 葛一鸣等编著,清华大学出版社
网友评论