概念:
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。看下关于的官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
类别:
OOM主要分为下面几种:
- 堆溢出
- 虚拟机栈和本地方法栈溢出
- 方法区和运行时常量池溢出
- 本机直接内存溢出
下面来通过小demo来模拟一下出现情况
堆溢出:
堆中存储的是对象或者数组,只要一直往堆中分配对象,就可以出现oom。为了方便测试,在运行时加上一些参数:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError 该参数表示堆的最小内存和最大内存为20m且不可扩展,在发生oom时自动生成快照文件,以便后续分析。
public class HeapOom {
private int[] numbers = new int[1 * 1024*1024];
public static void main(String[] args) throws InterruptedException {
List<HeapOom> heapOoms = new ArrayList<>();
while (true){
HeapOom heapOom = new HeapOom();
heapOoms.add(heapOom);
}
}
}
看一下输出结果:产生了oom,在oom的时候产生了堆的快照文件,以便后续分析。
虚拟机栈和本地方法栈溢出:
栈是由一个一个的栈桢组成的。一个方法调用的时候则会在虚拟机栈中创建一个栈桢,方法调用到结束的过程即为栈桢的入栈和出栈的过程。hotspot虚拟机中的栈内存是无法自动扩容的。这样的话,可能会有两种情况造成栈内存溢出
1 方法的调用太深,会导致栈桢创建的太多,耗尽了当前线程分配的栈的内存。例如死循环。为了方便测试,在运行时加上一些参数:
-Xss256k 该参数表示堆的最小内存和最大内存为20m且不可扩展,在发生oom时自动生成快照文件,以便后续分析。
public class StackOverFlowTest {
public static void main(String[] args){
test();
}
public static void test(){
test();
}
}
运行结果:出现StackOverFlowError
2 栈桢的大小过于大,导致了耗尽了内存。那什么情况会导致栈桢的大小大呢?栈是由程序计数器、局部变量表、操作数栈、动态链接组成。如果方法的局部变量很多的话,调用时创建的栈桢占用的空间比较大,就有可能造成栈溢出。
/** 通过申明大量的局部变量导致栈桢的空间大,超过可申请的空间,造成StackOverFlow
* -Xss256k
*/
public class StackOverFlowTest2 {
public static void main(String[] args){
test();
}
public static void test(){
long long1,long2,long3,long4,long5,long6,long7,long8,long9,long10,
long11,long12,long13,long14,long15,long16,long17,long18,long19,long20;
long1 =long2 = long3=long4=long5=long6=long7=long8=long9=long10
=long11=long12=long13=long14=long15=long16=long17=long18=long19=long20=0;
test();
}
}
注:*long占8个字节,要占到256k需要申明大量的局部变量,所以还是通过死循环的方法调用,如果调用次数变少也可间接的证明栈桢过大无法申请空间时会造成stackOverFlow。至于如何证明调用次数减少,可以定义一个静态变量,每次进入test方法时+1,由于oom程序会停止,所以可以通过try{}catch(Error e){} 来获得最终的调用次数,类似于下面这个:
方法区和运行时常量池溢出:
首先要知道方法区中存放的是什么:存放已被虚拟机加载的类的信息,常量、静态变量。
方法区溢出:
由于动态代理是通过生成代理类,然后加载到jvm中,所以可以通过不断的生成代理类来模拟方法区溢出