深入理解java虚拟机学习笔记(3)——OOM异常

OutOfMemoryError异常

一、Java堆溢出

Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达的路径来避免垃圾回收机制清除这些对象,随着对象的增加,总容量触及最大堆容量限制后就会产生内存溢出异常。

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {

    static class OOMObject{

    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();

        while(true) {
           list.add(new OOMObject());
        }
    }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid171000.hprof ...
Heap dump file created [28188846 bytes in 0.265 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:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)
	at com.cc.test.HeapOOM.main(HeapOOM.java:17)

解决异常的方法:

  1. 确定内存中导致OOM的对象是否是必要的,即分清楚是内存泄露还是内存溢出。
  2. 如果是内存泄露,找到对象是通过怎样的引用路径,与哪些GC Roots相关联,才导致垃圾收集器无法回收它们。
  3. 如果是内存溢出,应该检查虚拟机堆参数(-Xmx与-Xms)的设置,是否还有上调的空间,再从代码检查是否某些对象生命周期过长,持有状态时间过长,存储结构设计不合理等情况

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

《Java虚拟机规范》描述了两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  2. 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。

使用以下两种实验让虚拟机产生异常:

  • 使用-Xss参数减少栈内存容量
/**
 * VM args: -Xss128k
 */
public class JavaVMStackSOF {

    private int stackLength = 1;

    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

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

运行结果:

stack length:19408
Exception in thread "main" java.lang.StackOverflowError
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	at com.cc.test.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
	......
  • 定义大量本地变量,增大此方法帧中本地变量表的长度
public class JavaVMStackSOF2 {

    private static int stackLength = 0;

    public static void stackLeak(){
        long unused1,unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
        stackLength++;

        stackLeak();
        unused1=unused2= unused3= unused4= unused5= unused6= unused7= unused8= unused9= unused10=
        unused11= unused12= unused13= unused14= unused15= unused16= unused17= unused18= unused19= unused20=
        unused21= unused22= unused23= unused24= unused25= unused26= unused27= unused28= unused29= unused30=
        unused31= unused32= unused33= unused34= unused35= unused36= unused37= unused38= unused39= unused40=
        unused41= unused42= unused43= unused44= unused45= unused46= unused47= unused48= unused49= unused50=
        unused51= unused52= unused53= unused54= unused55= unused56= unused57= unused58= unused59= unused60=
        unused61= unused62= unused63= unused64= unused65= unused66= unused67= unused68= unused69= unused70=
        unused71= unused72= unused73= unused74= unused75= unused76= unused77= unused78= unused79= unused80=
        unused81= unused82= unused83= unused84= unused85= unused86= unused87= unused88= unused89= unused90=
        unused91= unused92= unused93= unused94= unused95= unused96= unused97= unused98= unused99= unused100=1;
    }

    public static void main(String[] args) {
        try {
            stackLeak();
        } catch(Throwable e) {
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

运行结果:

stack length:9244
Exception in thread "main" java.lang.StackOverflowError
	at com.cc.test.jvm.JavaVMStackSOF2.stackLeak(JavaVMStackSOF2.java:21)
	at com.cc.test.jvm.JavaVMStackSOF2.stackLeak(JavaVMStackSOF2.java:21)
	at com.cc.test.jvm.JavaVMStackSOF2.stackLeak(JavaVMStackSOF2.java:21)
	at com.cc.test.jvm.JavaVMStackSOF2.stackLeak(JavaVMStackSOF2.java:21)
	......
  • 每个线程分配的栈内存越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽,产生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) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

需要再32位系统上运行;在64位系统上运行并未产生OOM异常。

异常解决方法

  • 如果出现SOF异常,会有明确的错误堆栈进行分析。
  • 如果是因为建立多线程导致的内存溢出,在不能减少线程数量或者更换64位虚拟机时,可通过减少最大堆和减少栈容量来换取更多线程。

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

JDK7以前的虚拟机,常量池都是分配到永久代中;JDK7起,字符串常量池被移指堆中;到JDK8,则完全使用了元空间来代替永久代进行方法区的实现。

  • 需在JDK6上测试:通过-XX:PermSize和-XX:MaxPermSize限制永久代的大小,即可间接限制常量池的容量。
/**
 * VM args: -XX:PermSize=6M -XX:MaxPermSize=6M
 */
public class RuntimeConstantPoolOOM {

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

在JDK8上并未产生OOM,未在JDK6上试运行以上代码。

  • 由于JDK7及以上字符串常量池被移至堆中,因此通过改变-Xmx限制最大堆容量
/**
 * VM args: -Xmx6M
 */
public class RuntimeConstantPoolOOM {

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

JDK8运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.resize(HashMap.java:703)
	at java.util.HashMap.putVal(HashMap.java:662)
	at java.util.HashMap.put(HashMap.java:611)
	at java.util.HashSet.add(HashSet.java:219)
	at com.cc.test.jvm.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:15)
  • 运行时产生大量的类去填满方法区,使其溢出
/**
 * JDK7:-XX:PermSize=10M -XX:MaxPermSize=10M
 * JDK8:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
 */
public class JavaMethodAreaOOM {
    static class OOMObject{

    }
    public static void main(String[] args) {
        while(true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, args);
                }
            });
            enhancer.create();
        }
    }
}

未在JDK7上运行以上代码;由于JDK8已经完全移除了永久代,使用-XX:PermSize=10M -XX:MaxPermSize=10M则无效,运行以上代码不会出现OOM;使用-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M限制元空间的大小,出现了OOM异常,运行结果如下:

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:557)
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
	at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:387)
	at com.cc.test.jvm.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:28)
Caused by: java.lang.OutOfMemoryError: Metaspace
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:554)
	... 7 more

异常解决方法

  1. -XX:MaxMetaspaceSize设置元空间的最大值
  2. -XX:MetaspaceSize指定元空间的初初始空间大小
  3. -XX:MinMetaspaceFreeRadio减少因为元空间不足导致的垃圾收集频率
  4. -XX:MaxMetaspaceFreeRadio控制最大的元空间剩余容量的百分比

四、直接内存溢出

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

public class DirectMemoryOOM {

    private static final int _1MB = 1024 *1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while(true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at com.cc.test.jvm.DirectMemoryOOM.main(DirectMemoryOOM.java:19)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值