前面我们对虚拟机运行时内存结构进行了学习,接下来一起看看发生在不同的内存区域OOM异常的分析与解决办法。
-
Java堆溢出
Java堆内存溢出是开发中最常见的异常情况,堆溢出异常对战信息“java.lang.OutOfMemoryError”后面一般会有异常信息“Java heap space”。
堆内存溢出异常分析一般我们需要借助于内存印像分析工具(我使用的就是Eclipse Analyzer,对Dump出来的堆转储快照进行分析,确认出当前是内存泄露还是内存溢出,然后在区别分析:
如果是内存泄露,可以通过分析GC Roots引用链,根据引用链信息可以比较准确的定位出泄露代码位置,检查代码是否存在某些对象过大且生命周期过长,从代码层面优化内存消耗问题。
如果是内存溢出,我们需要检查下当前虚拟机的堆空间大小与机器物理内存,适当增加虚拟机的内存空间,增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m -
虚拟机栈和本地方法栈溢出
这里有两种异常情况:
1:线程请求栈深度大于虚拟机所允许的最大深度,导致OOM
2:虚拟机在扩展栈时无法申请到足够的内存空间,导致OOM
在单线程下这种异常一般抛出的是stackOverflowError异常,这里分析就没有什么好的办法了,只能去深入分析代码,观察是否存在死循环代码或者别的什么可能引起异常的代码。
在多线程的情况下一般抛出oom异常,并且告诉"unable to create new native thread".
这个异常我们首先需要判断下,发生内存溢出时进程中到底都有什么样的线程,这些线程是否是应该存在的,是否可以通过优化来降低线程数; 另外一方面默认情况下java为每个线程分配的栈内存大小是1M,通常情况下,这1M的栈内存空间是足足够用了,因为在通常在栈上存放的只是基础类型的数据或者对象的引用,这些东西都不会占据太大的内存, 我们可以通过调整jvm参数,降低为每个线程分配的栈内存大小来解决问题,例如在jvm参数中添加-Xss128k将线程栈内存大小设置为128k。 -
方法区和运行时常量池溢出
这是一种常见的内存溢出异常,报错信息一般会有“OutOfMemoryError: PerGen space”等信息,一般是由于对象等未被垃圾回收系统及时或收掉,导致内存持续增加切不释放,最后无内存空间可分配导致异常。
这里必须要讲到String.intern()方法,它的作用是:如果字符串常量池中已经包含了一个相同的String对象字符串,则直接返回此对象,不会从新分配空间。在我们正常编写代码过程中,要活用intern()方法,特别是对于字符串大对象。
解决方法区和运行时常量池溢出一般增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行: JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m" 如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。
清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。 -
本机内存溢出
这种异常有一个明显的特征是在Heap Dump文件中不会看见明显的异常,一般oom的dump文件很小且程序中直接或者间接的使用了NIO,很大可能性就是本机内存溢出。
以上只是给出常规OOM异常的分析和解决思路,由于不同的虚拟机的OOM也会有所差异,具体问题还是需要结合实际情况分析。
不喜勿喷,如有问题可以一起交流进步。
#参考:《深入理解Java虚拟机》