在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError异常的可能。
- java堆溢出
java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么对象数量达到最大容量限制后就会产生内存溢出异常。 例如:
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true) {
list.add(new OOMObject());
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3204)
at java.util.Arrays.copyOf(Arrays.java:3175)
at java.util.ArrayList.grow(ArrayList.java:246)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)
at java.util.ArrayList.add(ArrayList.java:443)
at pac01.HeapOOM.main(HeapOOM.java:15)
- 虚拟机栈和本地方法栈溢出
在java虚拟机规范中描述了俩种异常:
①如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
②如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
package pac01;
/**
* 虚拟机栈和本地方法栈OutOfMemoryError测试
* @author acer
*
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
oom.stackLeak();
}
}
在单线程的情况下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机都会抛出StackOverflowError异常
上面是基于单线程的情况下进行的测试,下面在多线程的情况下进行简单的测试:
package pac01;
/**
* 创建线程导致内存溢出
* @author acer
*/
public class JavaVMStackOOM {
private void donStop(){
while(true) {
}
}
public void stackLeakByThread(){
while(true) {
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
donStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM OOM = new JavaVMStackOOM();
OOM.stackLeakByThread();
}
}
通过不断建立线程的方法也会出现内存溢出异常,但是上面的情况出现的内存溢出异常并没有关联,在这种情况下,每个线程分配的内存越大,越容易出现内存溢出异常,原因是什么呢?操作系统为每个进程分配的内存是有限制的,如果java虚拟机每个线程分配到的内存越大,那么建立的线程数就越少,建立线程时就越容易将剩余的内存耗尽。如果建立过多的线程导致内存溢出异常,那么在无法减少线程数量并且无法更换64位虚拟机的情况下,那可以通过减少最大堆内存和减少栈容量来换取更多的线程数量。
- 方法区和运行时常量池溢出
package pac01;
import java.util.ArrayList;
import java.util.List;
/**
* 运行时常量池导致的内存溢出异常
* @author acer
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
//使用lsit保持着对常量池的引用,避免GC回收常量池的行为
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at.....
从运行结果可以看出,运行时常量池溢出,在OutOfMemoryError后面跟随的提示信息是: ”PermGen space",这说明运行时常量池属于方法区的一部分。