JVM内存GC的骗局——JVM不抛出OOM但内存已经泄露
- 2016-04-29 13:32:00
- 安大叔 原创
- 5946
概述
现状:
同事在模拟用户不停的发送请求给某系统,在运行一段时间后,突然,系统上邮件报告测试用例请求失败,登录测试系统的服务器,首先看下JVM的参数设置,如下:
-server –Xms4g –Xmx4g -XX:MaxPermSize=256m -verbose:gc -XX:+PrintGCDetails -Xloggc:$CATALINA_BASE/logs/gc.log -XX:+PrintGCTimeStamp,再使用TOP命令看看服务器发生了什么。
观察一段时间后,CPU一直运行在100%,于是想当然的认为可能是那段程序里面触发了BUG,有可能是正则表达式或者某段代码里面有个死循环的坑跳进去,没有出来。这不是很简单的事吗?直接使用jstack + pid 把堆栈打出来即可,直接操作吧,界面上马上输出操作日志,由于日志过多并且其他的日志类似,帮只截取部分如下:
从上面的堆栈日志可以看出,所有的线程都被BLOCKED住了,然后堆栈里面也找不到任何业务的相关代码,难道直觉出错了,感觉一下子不太好了,但是至少可以排查到不是上面的二种原因了,好吧,那再看看应用的GC的情况,部分日志如下。
解释一下:
第一行:
1403682.561: [GC [PSYoungGen: 1375104K->11376K(1386176K)] 4145665K->2782002K(4182400K), 0.0174410 secs] [Times: user=0.27 sys=0.00, real=0.02 secs]
发生的时间点,:JVM运行的时间长度,以度为单位,也可以格式化成固定的时间格式
0.02 secs:代表实际的GC的运行时间
注:上面总的运行时间小于用户态和系统态的时间总和,是由于后者仅指CPU的运行时间,包括等待或IO阻塞的时间,而且现在的GC是采用多线程收集的,同时机器也是多个CPU,因此,大部分是二者之和要比前面的值大,如果是采用串形化收集器( serial collector)的话,二者时间几乎相差不多。关于各种收集器的差别,后续有时间再安排详细总结。
接下来的二行,不再重复说明,第四行有Full字样,代表JVM发生了Full GC,不过多了二个分区的收集,PSOldGen:老生代的回收前后空间大小及总空间;PSPermGen:持久代的回收前后空间大小和总空间。从第三行,可以看出老空间的使用率达到饱和,从而触发了FULL GC,但是很遗憾的是第五行后又接着发生了FULL GC,后面的都是一直在持续进行,但是系统一直不抛出OOM异常或者进程退出,导致这台机器服务进程一直存在,但是基本无法正常工作。
GC,无论Young GC还是Full GC,每次都会导致JVM STW(STOP WORLD)暂停用户的业务工作,来处理垃圾回收任务,短时间内无法响应用户请求,特别是大量的Full GC会导致系统响应速度降低,另外还有OOM的巨大风险。Young GC频繁,就算GC采用多线程回收方式,尽管回收的时候非常短,但是如果GC次数和频率很高,因此对应用的影响是不可忽视的。 Full GC 包括整个分区的垃圾回收,包括新生代、旧生代、持久代等。因此其回收成本高,应用也会暂停更长时间,无法及时响应用户的请求,所以需要特别注意这个种情况,一般来讲,排除主动的调用GC操作外,JVM会在以下几种情况发生Full GC。
3. 统计新生代 GC晋升到旧生代的平均大小大于旧生代的剩余空间
解决:
第四行里面看不出实际的业务相关的,第五行到六行还是可以看出来的,那就先看第四行的对象包括什么具体的实例吧。
打开后,首页会给出可疑的建议对象实例,直接跳转到列表中,打开折叠细节即可看到真面目,里面包括了三十多万个对象,找相关的人员对根据业务需要,直接把不需要的实例在使用完后移除,其他几行的问题类似处理就即可。
总结:
从上面GC的发生的情况来看,JVM一次次不停的努力的帮我们进行GC操作,直接把CPU全部占光,但是就是不直接抛出异常直接告诉我们内存不够了,感觉把我们带了到一个巨大的庞氏骗局,也许我们把JVM的内存加大,这个坑还将帮我们隐藏下去,如果程序设置了定时重启之类的操作,这个坑就永远发现不了。一般产品开发人员非常希望应用程序能在用户发觉之前发现这个问题,JVM无法判断出这个问题,也就不能帮我们抛出几乎OOM的异常,不过可以通过调整GCTimeLimit和GCHeapFreeLimit参数来重新定义何时抛出OutOfMemoryError错误。GCTimeLimit 的默认值是98%,也就是说如果98%时间都用花在GC上,则会抛出OutOfMemoryError。GCHeapFreeLimit 是回收后可用堆的大小。默认值是2%。当然最好的办法就是开发工程师开始就很清楚如何使用相关的容器类的正确用法,并且在上线前能经过充分的测试或运行。本文只是引用GC方面的一个具体的安全来说明GC是怎么骗人的,关于GC和JVM内存相关的细节如何及时的发现此类的问题,有机会再通过示例和大家探讨学习。
注:以上资料仅以HOTSPOT VM 1.7.65 版本参考。
百测软件技术纠纷和解员:郝少龙 电话:18232183555 | 昌平区市场监督管理局天通苑南所投诉举报电话:010-64824876| 营业执照