深入理解JVM笔记-演示OOM异常和SOF异常

时间:2022-02-18 20:56:35

1.堆中的OutOfMemory异常

设置最大堆和Xmx最小堆Xms参数,通过不断的创建对象,当没有足够的空间创建新的对象时产生内存溢出异常

-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M

-XX:+PrintGCDetails

 -XX:SurvivorRatio=8


Xms:最小堆

Xmx:最大堆

Xmn:新生代的大小

PrintGCDetails:打印GC日志

SurvivorRatio:Eden区和Survivor比例

import java.util.ArrayList;
import java.util.List;

/**
 * 演示OutOfMemoryError
 * 需要指定VM参数
 * -verbose:gc
 * -Xms20M
 * -Xmx20M
 * -Xmn10M
 * -XX:+PrintGCDetails
 * -XX:SurvivorRatio=8
 */
public class HeapOOM {

    //静态内部类
    static class OOMObject{

    }

    public static void main(String[] args) {
        List<OOMObject> list=new ArrayList<OOMObject>();
        //通过不断的创建对象达到OOM
        while (true){
            list.add(new OOMObject());
        }
    }
}

输出,从出错信息java heap space中也可以看出异常是在堆中发生的:

[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->4857K(19456K), 0.0058096 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13049K->19432K(19456K), 0.0165642 secs] [Times: user=0.07 sys=0.01, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->10044K(10240K)] 19432K->10044K(19456K), [Metaspace: 3299K->3299K(1056768K)], 0.1272741 secs] [Times: user=0.30 sys=0.00, real=0.13 secs] 
[Full GC (Ergonomics) [PSYoungGen: 8019K->7829K(9216K)] [ParOldGen: 10044K->8415K(10240K)] 18063K->16244K(19456K), [Metaspace: 3312K->3312K(1056768K)], 0.1143881 secs] [Times: user=0.39 sys=0.01, real=0.11 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 7829K->7828K(9216K)] [ParOldGen: 8415K->8396K(10240K)] 16244K->16225K(19456K), [Metaspace: 3312K->3312K(1056768K)], 0.0771517 secs] [Times: user=0.36 sys=0.01, real=0.08 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
 PSYoungGen      total 9216K, used 8069K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
	at java.util.ArrayList.grow(ArrayList.java:261)
  eden space 8192K, 98% used [0x00000007bf600000,0x00000007bfde1540,0x00000007bfe00000)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
  from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
  to   space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
	at java.util.ArrayList.add(ArrayList.java:458)
 ParOldGen       total 10240K, used 8396K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
	at chapter2.HeapOOM.main(HeapOOM.java:20)
  object space 10240K, 81% used [0x00000007bec00000,0x00000007bf433318,0x00000007bf600000)
 Metaspace       used 3344K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 372K, capacity 388K, committed 512K, reserved 1048576K

关于设置VM参数:

Eclipse在Debug页签中设置VM arguments(可以直接参考深入理解JVM书中第2章)

IDEA:点击Edit Configuration..弹出的Run/Debug Conigurations中设置VM options

(1)首先在类文件中右键,选择create 'HeapOOM main()'...,创建Run/Debug Conigurations,在VM options中设置JVM 参数

深入理解JVM笔记-演示OOM异常和SOF异常

(2)之后,在这里的下拉列表中即可选择Edit Configuration..

深入理解JVM笔记-演示OOM异常和SOF异常

(3)设置VM Options

深入理解JVM笔记-演示OOM异常和SOF异常

2.虚拟机栈中的*异常

通过递归的方式,不断的调用stackLeak方法,来增加栈的深度,当栈的深度超过虚拟机允许的最大深度时将抛出SOF异常。

当线程执行一个方法时,会在Java虚拟机栈区创建一个栈帧,并将栈帧压栈,因此不断的执行stackLeak方法,可以增加栈的深度。

JVM参数设置:

-Xss160k

Xss设置栈容量,书中给出的是128k,实际运行时提示Xss最少要160k,因此设置为了160k

**
 * 演示*(栈深度达到虚拟机允许的最大深度时抛出的SOF异常)
 * 需要指定VM参数
 * -Xss160k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;


    /**
     * 递归方法,不断的调用自己可以增加栈的深度
     */
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
        try {
            //调用stackLeak方法,当栈深度达到虚拟机允许的最大深度时,抛出抛出*异常
            javaVMStackSOF.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + javaVMStackSOF.stackLength);
            throw e;
        }

    }
}

输出结果,当调用了771次stackLeak方法时,抛出了SOF异常

stack length:771
Exception in thread "main" java.lang.*Error
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
	at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
......

3.虚拟机栈中的OutOfMemory异常

操作系统分配给每个进程的内存是有限制的,操作系统内存限制减去Xmx和MaxPermSize的容量,程序计数器消耗内存很小,如果不计算虚拟机进程本身所耗内存,剩下的内存由虚拟机栈和本地方法栈瓜分,因此每个线程分配的栈容量越大,可以建立的线程数就越少,建立线程时就容易耗尽剩下的内存。

内存溢出产生方式:通过不断的创建线程去执行方法中的任务,线程越多,可用的内存就越少,直到内存耗尽,抛出OutOfMemroy异常。

VM参数,设置栈容量为2M:

-Xss2M

代码有风险,运行需谨慎!!!

代码容易导致系统假死,运行前请保存当前的工作。

/**
 * java虚拟机栈抛出OutOfMemory异常
 * 代码有风险,运行需谨慎!!!
 * 方法:通过不断的建立线程的方式导致内存耗尽产生OutOfMemory异常
 * VM参数:
 * -Xss2M
 * 来自深入理解Java虚拟机
 */
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 javaVMStackOOM = new JavaVMStackOOM();
        javaVMStackOOM.stackLeakByThread();
    }
}

4.运行时常量池OutOfMemory

由于JDK 1.7中将常量池移到了堆中,不再放在方法区中,因此VM参数适用于JDK 1.7以下的版本。

String.intern()方法作用:如果常量池中已经包含了一个等于此String对象的字符串,返回代表池中的这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中,并返回对象的引用。

产生内存溢出方式:通过不断的在常量池中添加对象,达到最大容量时,抛出内存溢出异常。

VM参数:

-XX:PermSize=10M

-XX:MaxPermSize=10M


PermSize:永久代大小

MaxPermSize:永久代最大容量

import java.util.ArrayList;
import java.util.List;

/**
 * 运行时常量池OutOfMemory异常
 * 方法:常量池在方法区中(JDK 1.7之前),通过String的intern方法不断的向常量池中添加对象,当没有足够的空间创建新的对象时抛出OOM异常
 * 虚拟机参数:
 * -XX:PermSize=10M
 * -XX:MaxPermSize=10M
 * 注意:1.7中常量池被移到了堆中,需要使用1.7之下的JDK
 * 来自深入理解Java虚拟机
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        //使用List保持常量池的引用,使对象避免Full GC
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            //通过intern方法不断的向常量池添加对象
            list.add(String.valueOf(i++).intern());
        }
    }
}

JDK1.7以上VM参数设置如下:

-Xms20m

-Xmx20m

异常信息:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.toString(Integer.java:403)
	at java.lang.String.valueOf(String.java:3099)
	at chapter2.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:22)

详细可以参考:

Java方法区和运行时常量池溢出问题分析

https://www.cnblogs.com/luoxn28/p/5425425.html


5.方法区OutOfMemoryError

方式:通过CGLib产生大量的动态类,由于方法区需要存放Class的相关信息,因此方法区的容量将会越来越少,直到抛出内存溢出异常。

VM参数:

-XX:PermSize=10M
-XX:MaxPermSize=10M

/**
 * 方法区OutOfMemory
 * 方法:通过CGLib,动态生成大量的类
 * VM参数:
 * -XX:PermSize=10M
 * -XX:MaxPermSize=10M
 * 来自深入理解Java虚拟机
 */
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, objects);
                }
            });
            enhancer.create();
        }
    }
}

JDK1.8中移除了永久代,取而代之的是元空间,因此可以通过设置元空间的大小,参数如下:

-XX:MetaspaceSize=10M

-XX:MaxMetaspaceSize=10M

异常信息:

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
	at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
	at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
	at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
	at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
	at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
	at chapter2.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:35)

参考:

Java 8: 从永久代(PermGen)到元空间(Metaspace)

https://blog.csdn.net/zhushuai1221/article/details/52122880