jvm认识与探究(三)JMM内存模型

一、概述内存模型区域

1.内存区域认识

a.内存模型区域分为两大块,一块是非堆区(Metaspace),一块是堆区(Heap)
b.堆区分为两大块,一块是Old区(老年代),一块是Young区(年轻代)
c.Young区也区分为两大块,一块是Survivor区(s0+s1),一块是Eden区Eden:s0:s1 = 8 :1:1,s0和s1一样大也可以叫做From和To

在这里插入图片描述

2.创建对象所在的区域

Java创建的对象存放于堆内存,一般情况下,新创建的对象都会分配到Young区中的Eden区,一些特殊的、大的对象会直接分配到Old区

例如有对象A、B、C,已创建,存放于Eden区,但是内存空间都有大小限制,Eden区有80M,但是已经全部使用完,当再次新建对象时,就会对Eden存放的对象进行清理(即:垃圾回收,请看垃圾回收篇)。清理之后大部分对象不再存在,但是仍然会有部分对象存活,这时就会把存活对象分配到Survivor区(即:s0或s1)。

3.Survivor区详解

有上图可知,Survivor又分为s0和s1,又可以叫做From和To,同一时间节点s0和s1两个区域中只有一个区域会存在数据。

从上一点得知Eden区空间不足时会进行垃圾回收(Eden区的垃圾回收叫Minor GC),每Minor GC一次会把Eden区的存活对象放进From区,下一次Minor GC又会把Eden区From区的存活对象放入To区 ,所以保证了s0或则s1其中一个区域一定有一个为空,并且每次Minor GC后存活的对象的分代年龄+1,当存活对象的分代年龄到达阀值(官方设置15),则会进入Old区,s0或则s1满了之后其中的所有对象也会进入Old区.。

4.Old区

从上文可知Old区都是分代年龄较大的对象或则到达Survivor区阀值的对象,并且当Old区空间不足时也会进行垃圾回收,称作:Major GC

5.非堆区

非堆区在JDK1.8只有被称为元空间(即:Metaspace),可以理解为方法区的实现。用于存放类的各种信息,另外非堆区也包含了运行时数据常量池。

6.对象分配流程图理解

在这里插入图片描述

7.jvisualVm查看内存模型

执行命令打开jvisualVm工具

jvisualvm

安装插件:
https://visualvm.github.io/pluginscenters.html —>选择对应版本链接—>Tools—>Visual GC

验证查看图形化界面和描述一致
在这里插入图片描述

二、内存溢出

1.堆内存溢出
	List<User> userList= new ArrayList<User>();

	@GetMapping("/heap")
    public String heap() throws InterruptedException {
        while (true){
            userList.add(new User());
            Thread.sleep(1);
        }
    }

设置启动参数:-Xmx20M -Xms20M

稍等片刻即可看到oom,也可以用jvisualVm观察堆内存情况
在这里插入图片描述
在这里插入图片描述

2.方法区内存溢出

pom.xml添加依赖

	<dependency>
        <groupId>asm</groupId>
        <artifactId>asm</artifactId>
        <version>3.3.1</version>
    </dependency>
public class MetaspaceUtil extends ClassLoader {

    public static List<Class<?>> createClasses() {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (int i = 0; i < 10000000; ++i) {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
                    "java/lang/Object", null);
            MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                    "()V", null, null);
            mw.visitVarInsn(Opcodes.ALOAD, 0);
            mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
                    "<init>", "()V");
            mw.visitInsn(Opcodes.RETURN);
            mw.visitMaxs(1, 1);
            mw.visitEnd();
            MetaspaceUtil test = new MetaspaceUtil();
            byte[] code = cw.toByteArray();
            Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
            classes.add(exampleClass);
        }
        return classes;
    }
}
	List<Class<?>> list = new ArrayList<Class<?>>();

    @GetMapping("/metaSpace")
    public String metaSpace() throws Exception {
        while (true) {
            list.addAll(MetaspaceUtil.createClasses());
            Thread.sleep(5);
        }
    }

设置启动参数:-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M

在这里插入图片描述

3.栈内存溢出
 	@GetMapping("/stack")
    public String stack(){
        stackMethod(1);
        return "stack";
    }

    public void stackMethod(long number){
        System.out.println(number);
        stackMethod(++number);
    }

执行结果
在这里插入图片描述

执行结果详解
Stack Space用来做方法的递归调用时压入Stack Frame(栈帧)。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出StackOverflow的错误。
-Xss128k:设置每个线程的堆栈大小。JDK 5以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线 程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有 限制的,不能无限生成,经验值在3000~5000左右。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更 大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值