JVM学习笔记1——Java内存区域与内存溢出异常

 一、内存区域

JVM学习笔记1——Java内存区域与内存溢出异常_第1张图片

JAVA内存区域可分为两大类:线程共享区和线程隔离区。

线程共享区:顾名思义是所有线程公有的一块区域,这块区域包含两个部分:方法区和堆

线程隔离区:每个线程独占一份的区域,这块区域包含三个部分:虚拟机栈、本地方法栈和程序计数器

 

方法区:存放类的元数据(类的方法代码,变量名,方法名,访问权限,返回值等等),常量和静态变量,以及及时编译器编译后的代码。

虚拟机栈:存储当前线程方法的执行情况,每个方法执行的时候都会创建一个栈帧,方法开始被调用到调用结束对应着栈帧的入栈和出栈的过程。栈帧会存储方法内的局部变量表,操作数栈(用来进行加减乘除等操作符计算的地方。比如int a= 1+2。1入操作数栈,2入操作数栈,计算和为3,3出操作数栈,将3复制给局部变量表中的变量a)、动态链接(指向方法区的该方法的元数据区域)、方法出口(方法的返回地址)等信息

本地方法栈:同虚拟机栈,不过针对的是本地方法,也就是native修饰过的方法

程序计数器:线程执行的字节码行号指示器(表示下一个该执行哪行代码)

堆:除去方法区的非基础数据类型的实例数据都在堆上存储(Class对象也在堆上存储)。堆同时分为新生代和老年代

 

老年代:无法在新生代分配的大对象,在新生代经过多次垃圾回收依然存活的对象可晋升至老年代

新生代:一般情况下对象直接在新生代分配空间(大对象除外)。新生代又分为两个Survivor空间和一个Eden

 

Eden:一直在使用,新生代gc时复制完后会清空该区域。空间大小一般为Survivor:Eden:Survivor = 1:8:1

Survivor:同一时间只有一个Survivor空间在被使用,另一个空闲状态。每当新生代gc时,会把Eden和使用中的Survivor空间中的存活对象复制到空闲Survivor中,gc结束之后,两个Survivor空间状态切换

 

二、内存溢出方法

首先区分一下平时排查问题时遇到的两个概念:内存泄漏和内存溢出

内存泄漏:不再使用的对象依然占据着内存空间无法被GC回收。(此时内存空间可能依然充足,只是泄漏问题一般在内存不足排查问题时才会被发现)。解决办法:改代码,不想改代码而又财大气粗的直接加内存空间

内存溢出:内存空间不足以支撑程序继续正常运行。解决办法:加内存空间,如果经费有限的可以考虑优化代码(没有bug的情况下 难度较大收益较小)。

1、方法区溢出:存储的类或常量信息太多。一般情况下不会出现,但是在CGLib等动态创建类的时候,可以关注该区域的内存使用情况。

JDK1.6之前的常量溢出导致的方法区溢出(为什么要求1.6,具体缘由可参见String类型的存储方式):

/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
// 方法区设置的越小越容易出现内存溢出,设置过大可能会抛int超过限制的异常
public class RuntimeConstantPoolOOM {
    public static void main(String[]args) {
        List list = new ArrayList();
        int i = 0;
        while(true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

类信息太多导致的溢出:

/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method,
                                        Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject {
    }
}

2、堆的溢出:对象实例过多,不停的创建gc不能回收的实例对象即可。

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List list = new ArrayList();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

3、虚拟机栈的溢出:栈帧过多导致溢出(无限递归调用)。栈帧过大导致溢出(局部变量非常多,存储的是基本数据类型,很难复现,理论情况下存在)。同时因为虚拟机栈是线程私有的,所以当线程过多,无足够内存分配时也会溢出(无限创建线程)。具体案例可自行思考,如有需要可留言。

你可能感兴趣的:(JAVA,JVM)