内存溢出异常
1.java堆溢出
当应用出现异常的时候根据异常堆栈信息“java.lang.OutOfMemoryError" 后 所跟的进一步提示”java heap space“可知道是堆内存发生了内存溢出。
要解决堆内存的异常,一般是先通过内存映像分析工具(如eclipse memory analyzer)对Dump出来的堆转储快照进行分析,分析的重点是确定内存中的对象是否是必要的,也就是弄清楚到底是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄露则可以进一步通过工具查看泄露对象到GCRoot的引用链,于是就能找到泄露对象是通过怎样的路径与GCRoots相关联并导致垃圾收集器无法自动回收它们。掌握了泄露对象的类型信息和GC Roots引用链的信息,就能比较准确的定位出泄露代码的位置了。
而如果不存在泄露,也就是堆内存中的对象都还必须存活着。有两个角度来尝试解决这个问题。一个是扩大堆的大小。要扩大堆的大小就应该去检查虚拟机的堆参数(-Xmx与-Xms),对比物理机的内存看是否还能调大堆内存。还有一个就是减小程序运行期的内存消耗,方法就是检查代码中是否存在某些对象的生命周期过长,持有状态时间过长的情况。
2.虚拟机栈和本地方法栈溢出
在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot虚拟机尽管-Xoss参数(设置本地方法栈)存在,但实际并不起效用。-Xss参数是设置最大虚拟机栈容量。
java虚拟机规范里描述了两种虚拟机栈和本地方法栈可能抛出的异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常。
- 如果虚拟机在扩展栈的时候无法申请到足够的空间,则将抛出OutOfMemoryError异常。
虚拟机栈存在最大虚拟机栈容量,这也就是说在单线程下无论你是通过调用多少方法,或者在一个方法里声明多少变量都只会造成栈溢出的异常而不会造成虚拟机栈的内存溢出异常。(当然除非你把-Xss参数设的比物理机内存还要大,不过没试过能不能这样设= =),要解决StackOverflowError一般可以尝试调大-Xss参数。
而内存溢出的异常一般是在多线程下建立了太多的线程的情况下产生,因为虚拟机栈是线程私有的,所以每建立一个线程,就需要建立一个虚拟机栈,而虚拟机栈在建立之初一般不会就是最大容量的,所以在创建了一定数量的线程之后完全有可能扩展容量的时候无法申请到足够的空间。
而要解决虚拟机栈的内存溢出,要不就是限制线程的数量,要不就是通过减小虚拟机栈的最大内存,绝对不能因为内存溢出而想增大虚拟机栈的最大栈容量,这样只会越来越糟。
3.方法区和运行时常量池溢出
运行时常量池属于方法区的一部分,方法区在jdk1.6及之前的版本中是通过堆中的永久代(PermGen)来实现的,可以通过虚拟机的-XX:PermSize( 永久代初始容量)及-XX:MaxPermSize(永久代最大容量)来设置永久代大小。
在-XX:PermSize=10M和-XX:MaxPermSize=10M的虚拟机参数下不同jdk下面代码执行情况并不一样。
// 使用List来保持对常量池的引用,避免被FullGC回收
List<String> list = new ArrayList<String>();
//10MB的PermSize在integer范围里足够产生OOM
int i = 3;
while(true) {
list.add(String.valueOf(i++).intern());
}
在jdk1.6及之前能很顺利的产生OOM异常,但在jdk1.7下却能循环非常久。因为在jdk1.7 中字符串常量池被移到了堆中,导致很难才会出现OOM异常。
方法区溢出也是一种常见的内存溢出异常,一个类要被垃圾回收器回收的条件是非常苛刻的,在一些经常生成大量Class的应用中,应该要尤其注意。例如动态生成大量jsp的应用,基于OSGI的应用。
4.直接内存溢出
DirectMemory容量可通过-XX:MaxDirectMemorySize指定,默认为最大堆容量(-Xmx指定)。
由DirectMomery导致的内存溢出一个明显的特征就是在Heap Dump文件中不会看见明显的异常,如果碰到OOM之后,发现Dump文件很小,而程序中又使用了NIO,那就可以考虑一下直接内存这方面的原因。