OutOfMemoryError实战,手动制造OOM

x33g5p2x  于2021-09-24 转载在 其他  
字(3.6k)|赞(0)|评价(0)|浏览(291)

Java堆溢出

Java堆用于存储对象实例,只要不断创建对象,保证GC Roots到对象间有可达路径避免垃圾回收机制清除这些对象

package cn.pengld;
import java.util.ArrayList;
import java.util.List;
/** * VM Args: -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError */
public class HeapSpaceOOMTest {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

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

由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,所以栈容量只由-Xss参数设定。在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是 StackOverflowError 异常。

当不限于单线程时,通过不断创建线程的方式可以产生内存溢出异常。

package cn.pengld;

/** * VM Args: -Xss2M */
public class StackSpaceOOMTest {
    private void keepGoing(){
        while (true){
        }
    }
    public void stackLeakByThread(){
        while (true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    keepGoing();
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) {
        StackSpaceOOMTest stackSpaceOOMTest = new StackSpaceOOMTest();
        stackSpaceOOMTest.stackLeakByThread();
    }
}

运行时常量池溢出(方法区的一部分)

String类是被final修饰的不可变类,更不能被继承,这样做的目的是为了性能,程序大部分的操作是针对字符串的,将字符串放在常量池中,能有效提高程序运行的效率。若大量的创建字符串对象,会导致常量池内存不够,导致常量池OOM,这也是在处理字符串拼接时使用 StringBuilder对象替代的原因。

package cn.pengld;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ConstantPoolOOMTest {
    /** * -XX:PermSize=10M -XX:MaxPermSize=10M JDK版本号小于 1.7 * Exception in thread "main" java.lang.OutOfMemoryError: PermGen space */
    public static void jdk6(){
        // 保持GC Roots的引用链,避免GC被回收
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
    /** * * 自JDK 7起,原本存放在永久代的字符串常量池被移至Java堆之中 * VM Args: -Xmx6M */
    public static void jdk7up(){
        // 保持GC Roots的引用链,避免GC被回收
        Set<String> list = new HashSet<>();
        short i = 0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
    public static void main(String[] args) {
        // use 1.6
// jdk6();
        // use 7+
        jdk7up();
    }
}

特别说明:笔者还原了此种情况,运行很多次OOM都发生在HashMap的resize()方法时,但具体的OOM异常取决于具体哪里的对象分配时发生了溢出。参照 《深入理解Java虚拟机:JVM高级特性与最佳实践》(周志明)第三版 2.4.3章节

方法区溢出

方法区的主要职责是用于存放类型的相关信息,如类 名、访问修饰符、常量池、字段描述、方法描述等。基本的思路是运行时产 生大量的类去填满方法区,直到溢出为止。不过在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0
 Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0

下面演示在JDK1.6时如何制造方法区OOM。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
package cn.pengld;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/** * 借助CGLib使得方法区出现内存溢出异常 jdk 1.6 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M */
public class MethodAreaOOMTest {
    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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject {
    }
}

本机直接内存溢出

参照《深入理解Java虚拟机-JVM高级特性与最佳实践》(周志明)第二版 2.4.4

package cn.pengld;

import sun.misc.Unsafe;
import java.lang.reflect.Field;
/** * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M */
public class DirectMemoryOOMTest {
    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);
        while (true) {
            unsafe.allocateMemory(_1MB);
        } }
}
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at org.fenixsoft.oom.DMOOM.main(DMOOM.java:20)

相关文章