JVM中的OOM异常

目录

Java堆溢出

虚拟机栈和本地方法栈溢出

方法区和运行时常量池溢出

直接内存溢出


Java堆溢出

Java堆中存储的是实例对象,只要不停的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象,随着对象数量的增加,超过堆的最大容量就会产生内存溢出异常。

        虚拟机参数为:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

//-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
public class HeapOOM {
    static class MyObject{

    }
    public static void main(String[] args) {

        List list = new ArrayList<>();
        while (true){
            list.add(new MyObject());
        }
    }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid35516.hprof ...
Heap dump file created [27754456 bytes in 0.085 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at HeapOOM.main(HeapOOM.java:21)

 

虚拟机栈和本地方法栈溢出

HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此-Xoss(本地方法栈容量)虽然存在,但实际上是没有效果的,栈容量只能由

-Xss(是指设定每个线程的堆栈大小)参数来设置,栈溢出分为两种情况

①如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowErrory异常。

②如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOtMemoryError异常 。

验证上述两种情况,

①可以使用 -Xss参数减少栈内存容量

结果:抛出StackOverflowError异常,异常出现时输出堆栈深度响应缩小

②定义了大量的本地变量,增大此方法珍重本地变量表长度。

结果:抛出StackOverflowError异常,异常出现时输出堆栈深度响应缩小

  • 本地方法栈1测试:
//-Xss256k
public class JavaVMStackSOF {                                     
    private int stackLength = 1;                                  
                                                                  
    public void stackLeak(){                                      
        stackLength++;                                            
        stackLeak();                                              
    }                                                             
    public static void main(String[] args) {                      
        JavaVMStackSOF stackSOF = new JavaVMStackSOF();           
        try {                                                     
            stackSOF.stackLeak();                                 
        }catch (Throwable e){                                     
            System.out.println("length:"+stackSOF.stackLength);   
            throw e;                                              
        }                                                         
    }                                                             
}                                                                 
             
运行结果:                                                     
length:1889
Exception in thread "main" java.lang.StackOverflowError
	at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
	at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)

//-Xss128k
运行结果:

Error: Could not create the Java Virtual Machine.
The stack size specified is too small, Specify at least 160k
Error: A fatal exception has occurred. Program will exit.
  • 本地方法栈2测试:
public class JavaVMStackSOF02 {
    private static int stackLength = 0;

    public static void test(){
        long unused01,unused11,unused21,unused31,unused41,unused51,unused61,unused71,unused81,unused91,
             unused02,unused12,unused22,unused32,unused42,unused52,unused62,unused72,unused82,unused92,
             unused03,unused13,unused23,unused33,unused43,unused53,unused63,unused73,unused83,unused93,
             unused04,unused14,unused24,unused34,unused44,unused54,unused64,unused74,unused84,unused94,
             unused05,unused15,unused25,unused35,unused45,unused55,unused65,unused75,unused85,unused95,
             unused06,unused16,unused26,unused36,unused46,unused56,unused66,unused76,unused86,unused96,
             unused07,unused17,unused27,unused37,unused47,unused57,unused67,unused77,unused87,unused97,
             unused08,unused18,unused28,unused38,unused48,unused58,unused68,unused78,unused88,unused98,
             unused09,unused19,unused29,unused39,unused49,unused59,unused69,unused79,unused89,unused99;
        stackLength++;
        test();
        unused01=unused11=unused21=unused31=unused41=unused51=unused61=unused71=unused81=unused91=
        unused02=unused12=unused22=unused32=unused42=unused52=unused62=unused72=unused82=unused92=
        unused03=unused13=unused23=unused33=unused43=unused53=unused63=unused73=unused83=unused93=
        unused04=unused14=unused24=unused34=unused44=unused54=unused64=unused74=unused84=unused94=
        unused05=unused15=unused25=unused35=unused45=unused55=unused65=unused75=unused85=unused95=
        unused06=unused16=unused26=unused36=unused46=unused56=unused66=unused76=unused86=unused96=
        unused07=unused17=unused27=unused37=unused47=unused57=unused67=unused77=unused87=unused97=
        unused08=unused18=unused28=unused38=unused48=unused58=unused68=unused78=unused88=unused98=
        unused09=unused19=unused29=unused39=unused49=unused59=unused69=unused79=unused89=unused99=0;

    }
    public static void main(String[] args) {
        JavaVMStackSOF02 stackSOF = new JavaVMStackSOF02();
        try {
            test();
        }catch (Throwable e){
            System.out.println("length:"+stackSOF.stackLength);
            throw e;
        }
    }
}

运行结果:
length:58
Exception in thread "main" java.lang.StackOverflowError
	at JavaVMStackSOF02.test(JavaVMStackSOF02.java:21)
	at JavaVMStackSOF02.test(JavaVMStackSOF02.java:22)
	at JavaVMStackSOF02.test(JavaVMStackSOF02.java:22)

结果表明:无论是由于栈桢太大还是虚拟机栈容量太小,当新的栈桢内存无法分配的时候,HotSpot虚拟机都会抛出StackOverflowError异常。   

  • 通过创建线程导致OOM  
public class JavaVMStackOOM {
    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) throws Throwable{
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
运行结果:
OOM

操作系统给每个进行分配的内存是有限的,虚拟机 最大内存减去堆内存、方法区内存剩下的就java中虚拟机栈内存和本地方法栈内存,线程创建时会分配虚拟机栈和本地方栈和程序计数器,因此每个线程分配到的栈内存越大,则能创建的越少。

方法区和运行时常量池溢出

方法区溢出:方法区的主要职责是用于存放类型和相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。可以通过运行时大量的产生类去填充方法区的方式来验证溢出。

 

字符串常量池溢出:Java7以后字符串常量池被移至Java堆中

--Xmx6m
public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        Set set = new HashSet();
        short i = 0;
        while (true){
            set.add(String.valueOf(i++).intern());
        }
    }

}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.resize(HashMap.java:704)
	at java.util.HashMap.putVal(HashMap.java:663)
	at java.util.HashMap.put(HashMap.java:612)
	at java.util.HashSet.add(HashSet.java:220)

String.intern()首次 原则:如果字符串常量字符串常量池中,则调用String.intern()返回结果,如果不在,看堆中有没有,没有则在字符串常量池中创建一份,有则返回堆的地址。

 

直接内存溢出

直接内存的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不指定就默认与Java堆最大值一致。

下面是使用Unsafe类模拟直接内存异常。

//-Xmx20m -XX:MaxDirectMemorySize=10M
public class DirectMemoryOOM {
    private static final int _1MB = 1024*1024;

    public static void main(String[] args) throws Exception{
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe= (Unsafe)unsafeField.get(null);
        int index = 0;
        while (true){
            unsafe.allocateMemory(_1MB);
            System.out.println(index++);
        }

    }
}
运行结果:
Java8下未发生OOM

 

你可能感兴趣的:(Java虚拟机)