• JVM第二话 -- JVM内存模型以及垃圾回收


    1.JVM内存模型

    1.1 Java对象布局

    在这里插入图片描述

    对齐填充:为了保证对象的大小为8字节的整数倍,自动对齐,避免多次加载

    1.2 Heap堆内存分布

    • 首先先使用jdk/bin/jvisualvm.exe的工具,也可cmd窗口直接输jvisualvm。打开以后在工具-插件安装Visual GC,监控一下当前已启动的java服务
      在这里插入图片描述

    从图中可以分为Metaspace元数据(非堆)、Old区老年代、Eden(Survivor0、Survivor1)区新生代也简称Young区

    1.2.1 Old区老年代

    • 每经过一次GC回收,对象年龄+1,大于15次没有被回收掉的则加入老年代中
    • 对象内存大小大于新生代,则直接加入到老年代中

    1.2.2 Young区新生代

    因为新生代绝大多数对象生命周期比较短,经过回收会导致Young区空间不连续,造成空间碎片的问题。
    在这里插入图片描述
    当给需要多个内存格的对象进行分配时无法分配,则会造成GC回收导致和CPU抢时间片。

    于是将Young区在分成Eden区和Survivor区

    • Eden区只存放新生的对象
    • 只要经过回收的就放入S区,Eden区就能够相对连续了,但是再次回收后的S区也会出现空间碎片问题,还是会导致对象放不下
    • 于是区分了S0(Survivor From)、S1(Survivor To),每次回收都转移到下一区域,永远保证S0或者S1有一个为空,由此解决碎片问题
    • Eden区和S0、S1的比例是8:1:1
    • 万一新生成的对象比较大,S0、S1内存不够了,需要向老年区借用内存,这称为担保机制

    在这里插入图片描述
    始终会保证S0或者S1有一个为空

    2.JVM 垃圾回收

    2.1 Young GC

    包含了Eden、S区,最小的GC有称为Minor GC

    2.2 Old GC

    Major GC,通常会伴随着Minor GC,相当于Full GC

    2.3 Full GC

    Young GC+ Old GC (+MateSpace GC)

    会导致 stop the work , 要尽可能减少Full GC的频率
    允许一定范围的Young GC

    2.4 模拟Heap区 OOM

    • 首先准备代码
    List<User> str = new ArrayList<>();
    
    @GetMapping("/while")
    public void while1() {
        while (true){
            str.add(new User());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 设置JVM堆内存大小
    -Xms40M -Xmx40M
    
    • 1
    • 堆内存分布和日志截图
      在这里插入图片描述
      上图垃圾回收过程:
    • 新生成一个对象进入Eden区,发现Eden区内存不足,则触发Young GC
    • Young GC首先对S1进行回收,未被回收的分代年龄+1,转移到S0或者Old区(满足分代年龄的)
    • 然后再对Eden区进行回收,未被回收的转到S0,从而给新对象预留空间
    • 经过回收后,Eden区或者S0或者Old区空间不够均会导致OOM

    2.5 模拟Metaspace区 OOM

    Java8使用Metaspace来替代永久代。Metaspace是方法区在Hotspot中的实现,并不在虚拟机内存中而是直接使用本地内存。

    在Java8中,Class、Metadata被存储在Metaspace元数据中,永久代(Java8后被Metaspace取代)存放了一下信息:
    1.虚拟机加载的类信息
    2.常量池
    3.静态变量
    4.即时编译后的代码

    如要模拟Metaspace空间溢出,可以不断的生成代理类往元空间加,那么久会触发Metaspace OOM

    • java 代码
    public class MetaspaceTest {
    
        static class User {
            String name;
        }
    
        public static void main(String[] args) {
            int i = 0;
            try {
                while (true) {
                    i++;
                    Enhancer enhancer = new Enhancer();
                    enhancer.setSuperclass(User.class);
                    enhancer.setUseCache(false);
                    enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, args));
                    enhancer.create();
                }
            } catch (Throwable e) {
                System.out.println("i=" + i);
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • jvisualvm工具监控和日志
      在这里插入图片描述
      至于Metaspace为什么没有填满,因为在Java8中Metaspace并不在虚拟机内存中而是直接使用本地内存

    3.JVM垃圾回收器

    3.1 怎么确定是垃圾对象

    1. 引用计数,可能会存在循环引用问题而导致两个对象都不能被回收
    2. 可达性分析,确定一个GC Root,由它出发,检查某个对象是否可达,不可达的则为垃圾对象

    3.2 GC Root

    • 虚拟机栈中的局部变量表( 栈->来源于方法区正在执行的变量)
    • 方法区的常量或者static变量,本地方法栈中的变量
    • 类加载器
    • Thread

    3.3 回收算法

    1.标记清除算法(存活对象、标记对象、未被使用空间)

    • 扫描过程中确定为垃圾对象,则打上标记,下次扫描才被回收
    • 标记和清除都扫描整个堆内存空间,比较耗时,清除后导致空间碎片不连续

    2.标记复制算法

    • 划分出一块区域,在标记后的同时将存活的对象复制到新的区域中,然后直接清空整个标记区域,等同于s0/1
    • 虽说达到空间连续了,但也比较浪费空间

    2.标记整理算法

    • 在标记后进行清除的过程中移动存活的对象

    3.4 垃圾收集器

    对上述算法的应用,将不同的回收算法适用于不同的代
    新生代:标记复制算法,老年代:标记清除、整理算法

    3.4.1 Serial GC

    -XX:+UseSerialGC

    • 单线程垃圾收集器,比较适用单核CPU,内存空间比较少
    • 适用新老年代
    • 缺点:会暂停业务代码线程,因为堆中的内存地址会变动,不能让线程使用到已经不存在的对象

    3.4.2 Parallerl GC

    -XX:+UseParallerlGC

    • 多线程,但会使应用程序暂停,适用新老年代

    3.4.3 Concurrent Mark Sweep(CMS)

    -XX:+UseConcMarkSweepGC

    • 使用的是标记清除算法,存在空间碎片问题,只适用于老年代
    • 应发类的垃圾收集器,用户线程和垃圾回收线程同时进行,低停顿时间

    3.4.4 G1 Garbage Collector

    -XX:+UseG1GC

    • 尽可能满足用户设置的停顿时间目标,很好的解决了空间碎片问题,吞吐量高
    • 用的标记整理算法,适用新老年代

    3.4.5 Z GC

    -XX:+UseZGC

    • 非常小的停顿时间,但是会牺牲一些吞吐量

    4. JVM调优纬度

    基于业务来确定使用最合理的垃圾回收期,主要有以下几个纬度

    • 串行:只有一个垃圾收集器,Serial
    • 并行:多个垃圾收集器,业务代码线程会停顿 Parallerl GC,比较关注吞吐量
    • 并发:多个垃圾收集器,业务代码共同运行,CMS、G1、ZGC,比较关注停顿时间

    4.1 停顿时间

    服务接口的响应时间,垃圾收集器进行垃圾回收和Client执行响应的时间

    4.2 吞吐量

    时间段内完成的请求数或者更新数,运行用户代码时间 /运行用户代码和垃圾收集总时间

    以上就是本章的全部内容了。

    上一篇:JVM第一话 – JVM入门详解以及运行时数据区分析
    下一篇:JVM第三话 – JVM性能调优实战和高频面试题记录

    书山有路勤为径,学海无涯苦作舟

  • 相关阅读:
    从头开始构建GPT标记器
    jsp美食共享平台系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
    特殊矩阵的压缩存储(对称矩阵,三角矩阵和三对角矩阵)
    i.MX 6ULL 驱动开发 三:字符设备驱动框架实现和调试
    Android Studio 快捷键及使用技巧汇总
    栈与队列--删除字符串中的所有相邻重复项
    postman的断言、关联、参数化、使用newman生成测试报告
    PMP考试中常见的英文缩写
    微服务网关之Zuul1.0上
    点云数据转pnts二进制数据
  • 原文地址:https://blog.csdn.net/qq_35551875/article/details/125516667