JVM学习(三) 学习内存溢出错误的方式

了解内存溢出的方式

为什么要尝试异常,意义在于知道异常错误发生的原因,知道如何触发,则遇到问题时候也能掌握方向,而不是一昧蒙头寻找答案

提前参数要素:
IDEA:-verbose:gc 用于打印gc情况

-Xmx:最大堆大小

-Xms:初始堆大小

-Xmn:年轻代大小

-Xss: 设置栈内存容量

-Xmx :最大内存

-Xmn :最小内存

-MaxPerMize :最大方法区容量

具体堆设置

https://blog.csdn.net/zfgogo/article/details/81260172

java 堆溢出

前面提及了,java堆主要用于存储实例对象,造成异常的主要方式是:

不断创建对象实例,且保证GC Roots(**实际上是垃圾收集器所要收集的对象**)到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆容量限制之后则产生内存溢出异常

/**
*VM Args :  -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError(这句会在抛出异常错误时生成HeapDump)
*/
public class HeapOOM {
    static class OOmObject {

    }

    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        while (true) {
            list.add((new OOmObject()));
        }
    }
}

一、基于MAT分析HeapDump

https://cloud.tencent.com/developer/article/1379028

https://www.cnblogs.com/wyb628/p/8567610.html

Shallow Size

Shallow Size是对象本身占据的内存的大小,不包含其引用的对象。对于常规对象(非数组)的Shallow Size由其成员变量的数量和类型来定,而数组的ShallowSize由数组类型和数组长度来决定,它为数组元素大小的总和。

Retained Size

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C就是间接引用) ,并且排除被GC Roots直接或者间接引用的对象

换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。


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

在当前默认 1.8虚拟机下(HotSpot),本地方法栈与虚拟机栈并不区分开来

根据规范:
这里存在两种异常:

① 线程请求的栈深度超过虚拟机所允许的最大深度,则抛出 StackOverflowError

② 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError

一、以最小栈内存108k为测试值(单线程)

//不能再小了,否则出现:The stack size specified is too small, Specify at least 108k

原理:通过不断进行递归方式不设置出口,来消耗栈内存,达到触发错误的效果

Tips:此处可以在方法内部定义足够多的本地变量或定义较少的栈内存,用于减少堆栈深度,从而使目标错误发生速度加快


/**
 * VM Args: -Xss128k
 */
public class JVMSingleThreadTest {
    private int stackLength = 1;

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

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


二、以最小栈内存108k为测试值(多线程)


/**
 * VM Args: -Xss128k
 */
public class javaVMMutiError {


    public static void main(String[] args) {
        while (true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    new JVMSingleThreadTest().stackLeak();
                }
            });

            thread.start();
        }
    }

}

其实此处可以不限定参数,若你的计算机内存容量较大,可能需要一段时间才能导致内存溢出(这里不考虑32位计算机)

也可能先出现这种情况	
Native memory allocation (malloc) failed to allocate 32744 bytes for ChunkPool::allocate
本段总结:

方法一我也尝试了添加足够本地变量,结果发现,还是无法出现,第二种方法达到的内存溢出错误

哪怕最后面堆栈深度为1,也不能出现内存溢出的问题。而方法2则能存在达到内存溢出的错误,尝试设置栈内存容量为200M或更高会使得出现错误的可能增加

证实:

①单线程下,无论是 栈帧 太大还是栈容量不够都只会触发爆栈错误(StackOverflowError

②多线程下,尝试将栈内存设置越大,内存溢出就会越容易发生。

内容描述:

操作系统给予每个线程所分配的内存容量为A,A 减去最大堆容量(Xmx控制),减去方法区容量(MaxPerMize)

,程序计数器所需要的内存可以忽略,则剩余的就归属虚拟机栈与本地方法栈

	总量一定的情况:
    线程数 T = 总内容容量 /每个栈定义的容量大小

	则出现一种,对栈深度与线程数量的平衡关系

栈操作描述:

栈结构应该知悉,先进后出,不断将方法压入,直到栈内存容量不足以分配,则出现爆栈问题(StackOverflowError),
其实与上面线程分配类似,为方法压栈,需要占用一定栈内存,栈的深度则成为:
	
	栈深度 S = 栈内存容量/压栈方法所占用内存情况

你可能感兴趣的:(JVM)