写在前面
java内存申请和释放均是由jvm在控制。而释放往往会出现各种各样的问题,经常一个引用没处理好就引起内存泄漏,最后引发OOM。如果发生在重要业务系统还可能出现严重的生产事故。 因此内存使用一定要谨慎,特别是引用要及时断链。虽然jvm有GC(垃圾回收引擎),但只能清理没有引用的对象,因此对象在不使用时及时置null。当然,笔者对此是很小心的,但万万没想到还是遇到了fullGC(old GC)。
定义
jvm的GC
通常遇到的是堆区内存问题,也就是堆GC。jvm的GC发展到现在也经历很多发展了,现在主要还是分代回收的思路。
eden space
伊甸园,新生对象放置的地方。
survivor space
在jvm中的被回收过对象仍存活的地方。
old Generation
幸存区被清理后仍活着的对象就步入老年了。 大多数GC问题都在老年代中被发现,笔者遇到的问题也是在这里。
eden 和survivor都属于新生代,与old gen有明显差别,也就是这样决定了java程序不用主动回收也能在较好的状态运行。 eden和survivor空间相对old generation小很多,这也意味着存放的内存也少很多。jvm一般将长期引用(jvm会将对象分析其引用计数)的对象(多次GC仍存活的)放在Old generation中。
jvm堆区不同区域使用的是不同算法,eden和survivor的GC属于miniGC,也就是局部GC,特点是速度快,低延迟(1.标记复制算法与空间小的优势,2.GC时需要将所有线程挂起Stop The World)。而old generation发生GC则为FullGC,且old generation用的是标记整理算法,线程扶起时间会很长。频繁的fullGC可能是程序设计不合理或内存参数不合理。
言归正传
这两天笔者遇到一个问题导致了fullGC发生。将进程进行dump分析 jmap命令配合工具 MAT(eclipse提供)发现是部分局部变量占用太多空间未及时释放造成并且占用了55%以上的内存空间。 为什么局部变量还会未及时释放?查阅源代码发现所有引用处均未发现被全局对象、静态对象引用。ArrayList和LinkedList占用内存过高,但也未被全局对象和静态对象引用。
感觉程序被幽灵附身了!!! 不存在的,直觉告诉我这种问题肯定在自己的程序上。继续分析果然发现了问题所在:发现对象内的值关联性非常强,继续分析最终找到数据库查询参数。会不会是数据造成?面对这个疑问diff了最近数据的情况。
芦山真面目
最近数据量暴增! 数量增加了6倍+,空间增加了3倍+。
由于数据结构和业务逻辑的特殊性将同条件的数据全部查出来处理了,由于数据全是相互存在引用,eden空间无法分配进行了miniGC。miniGC后空间仍然不足,就这样新生对象直接进入old generation。才生出来就进入老年 >_
解决方案
知道了原因就立即分析得到了解决方案。尽量不要构建局部的大对象!!!因此将DB中查询出的量控制住,增加中间对象的数量,用完即置null。使得eden中及时GC,old generation增长稳定。 修改程序代码再将压一下,发现old generation增长缓下来了,问题解决。
如果遇到类似问题还可以用jconsole和jvisual两款工具实时监控jvm状态、对象实例指标等。
不好的消息:jdk9已将jvisual去除,要使用jvisual请使用jdk8。jdk10中jconsole也没了。。 jmap分析吧。。。