背景
我们有个发送短信告警的服务,在线上环境跑着跑着就挂了,发不出短信,但重启后又能正常接着跑,只是跑了差不多两三个小时后就又会挂,当时运维不懂只能定个闹钟提醒自己去重启该服务,后来由我排查这个问题。
当时环境:
JDK版本:jdk1.6.0_10
系统:windows
排查
发送短信服务挂了是会有日志,找运维帮忙查看日志
看了日志,说是报了OOM,当时也不是很清楚这块,第一次遇到这种问题,百度谷歌查了很久,发现内存不够用的时候服务就会挂,于是就想是不是分配给Java服务的内存不够,而Java服务启动是可以指定分配给JVM的堆的大小的,当时那台机器的配置很高,内存有32G,于是就直接分配给了JVM 5G的堆内存,而初始的堆内存是1G。使用这个命令:
java -Xms1024m -Xmx5120m -jar demo.jar
这样启动是有效果的,过了两三个小时还能继续发短信,以为就没问题了,于是高枕无忧的去睡觉,隔天回来运维大哥说凌晨短信又发不出了,接着看日志,发现又是OOM。敢情这样操作还是会挂,只是把挂的时间延长了而已。。。
意识到得搞明白原理才能彻底解决这问题。先是了解了OOM的知识。
内存溢出与内存泄漏
- 内存溢出(Out Of Memory):系统已经不能再分配出你所需要的空间,存储的数据超出了指定空间的大小
- 内存泄漏(Memory Leak):程序在申请内存后,无法释放已申请的内存空间,导致不断创建新对象而又不被垃圾回收,被占有空间越积越多
内存溢出的原因有很多种,从上面日志图片可以知道发生内存溢出的区域是Java heap space,说明是堆的溢出,而其中内存泄漏是会导致内存溢出的。
一开始的做法以为没有内存泄漏的问题,觉得只要加大堆内存就可以,但结果也知道,加大堆内存只是延长了OOM出现的时间,这说明是存在内存泄露的。那具体是哪块代码呢?
先要知道有哪些Java线程,然后打印出对应的Java线程的堆栈信息,分析堆内存中占有空间最大的是那几个类。
- 查看当前已启动的JAVA进程和对应的PID
jps -l
- 查看堆栈信息,对应的线程的状态
jstack pid
- 生成java堆中对象的相关信息,包含对象实例数量以及占用的空间大小并输出到jmap.log文件
jmap -histo:live PID > /tmp/jmap.log
- 打印出某个java进程的jvm dump文件
jmap -dump:format=b,file=heap.bin <pid>
- 也可以读取名为 heap.bin的文件,并监听7000端口,可在浏览器localhost:7000打开
jhat -J-Xmx512m heap.bin
(因为有时dump文件很大,所以加了-J-Xmx512m参数)
获取到dump文件是需要通过工具来分析的,我是用MAT,也可以用visualVM。我这里用的是MAT。
经过MAT分析, 这下子一目了然:
sun.net.httpserver.ServerImpl$ServerTimerTask实例占了堆内存 88.33%,很明显这个是有内存泄漏的。我在JDK源码中找到这个类,同时debug了一遍,也找到了对应JDK BUG清单,证实了这个bug的存在:
bugs.java.com/bugdatabase…
说下JDK1.6源码造成内存泄漏的原因和解决方法:
- 每当客户端与
HttpServer连接时,HttpConnection实例会被加入到Set<HttpConnection> allConnections中。那么当一个连接关闭时,理应将其从allConnections中移除。 - 但
ServerTimerTask只处理超时连接,检查和移除那些在idleConnections中的连接。如果一个连接是bad request,它不会被添加到idleConnections,因此不会被ServerTimerTask移除。、 - 由于bad request的
HttpConnection从未被移除,它们将持续存在于allConnections集合中,导致内存泄漏。这样,随着时间的推移,连接的数量会不断增加,即使它们已经关闭。从而造成了内存泄漏、 - 链接给出了解决方法,就是遍历
allConnections的所有连接,并检查是否已经关闭,将已关闭的从allConnections里移除,将未关闭的连接收集到一个list,然后clear掉。 - 这个bug在后续版本 com.sun.net.httpserver.HttpServer 被修复了
另提
- 在生产中一般将-Xms和-Xmx设为为一致,是为了避免频繁扩容和GC释放堆内存造成的系统开销/压力。如果-Xms起初值设置的比较小,因为内存小,所以JVM需要不断回收以释放内存就会频繁触发GC操作,当GC操作无法释放更多内存时,才会进行内存的扩充。