背景
在解决某次现场问题时遇到了内存泄漏,通过对平台进行定位分析,并结合之前的经验整理了此文档。
问题分析
java.lang.OutOfMemoryError这个报错相信是很多做java开发人员的噩梦,特别对于一些新人程序员来说,第一次遇到往往思路全无,其实产生该错误的原因大多出于以下原因:JVM内存过小、程序逻辑不严密、产生过多垃圾。
一、常见的错误提示
- tomcat:java.lang.OutOfMemorryError: PermGen space
这一部分用于存放Class和Meta的信息,Class在被Load的时候被放入PermGen space区域(包括常量池:静态变量),它和存放Instance的Heap区域不同,GC不会在主程序运行期间对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。
这种错误常见在web服务器,可以通过设置jvm启动参数来解决: -XX:MaxPermSize=256m
- tomcat:java.lang.OutOfMemoryError:java heap space
这部分用于存放类的实例。被缓存的实力(Cache)对象,大的map,list引用大的对象等等,都会保存在这里。
堆内存会在jvm启动时自动设置,初始值为 -Xms为物理内存的1/64,最大值 -Xmx为1/4; 可以通过参数 -Xmn 、-Xms、
-Xmx设置,一般-Xms、 -Xmx不超过80%,-Xmn为-Xmx的1/4
- java:java.lang.OutOfMemoryError
这部分用于存放局部变量、方法栈帧信息。栈帧太多,也就是函数调用层级过多时就会出现此异常,需要检查是否有死递归的情况。
对应的启动参数为: -Xss(JDK1.5之后默认是1M,之前是256K)
实例,以下是1G内存环境下java jvm的参数设置参考:
JAVA_OPTS="-server -Xms800m -Xmx800m
-XX:PermSize=64M -XX:MaxNewSize=256m
-XX:MaxPermSize=128m -Djava.awt.headless=true
导致内存溢出的常见原因
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
- 集合类中有对对象的引用,使用完未清空,使得JVM不能回收
- 代码中存在死循环或循环产生过多重复的对象实体
- 使用的第三方软件中的BUG
- 启动参数内存设定的过小
解决内存溢出的方法
1) 增加jvm的内存大小
- 在执行某个class文件的时候,可以使用java -Xmx256 aa.class来设置运行aa.class时jvm所允许占用的最大内存为256M
- 对tomcat容器,可以在启动时对jvm设置内存限度。对tomcat可以在catalina.bat中添加:
set CATALINA_OPTS=-Xms128M -Xmx256M
set JAVA_OPTS=-Xms128M - Xmx256M
或者把%CATALINA_OPTS%和%JAVA_OPTS%代替为Xms128M -Xmx256M
2)优化程序,释放垃圾构造方法等
主要包括避免死循环,应该及时释放各种资源:内存、数据库连接、防止一次性载入太多数据。导致java.lang.OutOfMemoryError的根本原因是程序不健壮。因此,从根本上解决java内存溢出的唯一方法就是修改程序,及时释放没有用的对象,释放内存空间。遇到该错误的时候要仔细检查程序,需要重点排查以下几点:
- 检查代码中是否有死循环或者递归调用。
- 检查是否有大循环重复产生新对象实体。
- 检查对数据库查询中,是否一次性获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就可能引起内存溢出。因此对于数据库查询尽量采用分页的查询方式。
- 检查List、Map等集合对象是否使用完之后,未清除。List、Map等集合对象会始终存有对对象的引用,是的这些对象不能被GC回收。
dump排查方案
以上方法都无法快速定位问题或者解决时,可以尝试使用dump文件进行问题的深度定位。jvm启动时添加参数
-XX:+HeapDumpOnOutOfMemoryError,这样当内存溢出时,会生成dump文件。通过dump文件使用专门的分析工具可以查看类的使用情况。推荐使用工具Eclipse Memory Analyzer。当然也可以使用Jprofile进行动态实时分析。两个工具网上都有详细教程:
http://blog.csdn.net/chendc201/article/details/22897999
http://essen.iteye.com/blog/1825314
经验总结
内存溢出不像普通的缺陷,可以通过简单的功能测试被发现,大多数的内存问题都是在系统运行相当一段时间后才显露出来,因此我们需要在平时去重视,建议将部分的要求纳入代码审核工作的检查项中,有目的有方向的检查执行,就可以大大降低内存溢出的风险。