在Java虚拟机规范中,除了程序计数器外,虚拟机内存的其他几个运行时数据区域都有可能发生OutOfMemoryError异常的可能。
单个线程下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。
虚拟机可以设置堆和方法区的大小,程序计数器消耗的内存很小,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就容易把剩下的内存耗尽。
一、Java堆溢出
Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么对象数量到达最大的容量限制后就会产生内存溢出异常。public class HeapOMM {
static class OOMObject {
}
public static void main(string[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
二、虚拟机栈和本地方法栈溢出
单个线程下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。
public class Test {
private int stackLength = 1;
public void StackLeak(){
stackLength++;
StackLeak();
}
public static void main(String[] args) throws Throwable{
Test oom = new Test();
try {
oom.StackLeak();
} catch (Throwable e){
System.out.println("Stack length:" + oom.stackLength);
throw e;
}
}
}
虚拟机可以设置堆和方法区的大小,程序计数器消耗的内存很小,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就容易把剩下的内存耗尽。
public class Test {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread(){
while (true) {
Thread thread = new Thread( new Runnable(){
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args){
Test test = new Test();
test.stackLeakByThread();
}
}
三、方法区和运行时常量池溢出
JDK1.6中String.intern()是一个Native方法,作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中。JDK1.7的intern()不会复制实例,只是在常量池中记录首次出现的实例的引用。public class Test {
public static void main(String[] args){
// 使用List来保持常量池的引用避免被回收
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}