JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS

文章目录

  • 前言
  • 一、GC(Garbage Collector)Tuning 垃圾回收器
    • 1、什么是垃圾
    • 2、java与C++的区别
    • 3、how to find a garbage?
      • a、reference count 引用计数法(java不用)
      • b、Root Searching 根可达算法(java用)
    • 4、引用的两次标记过程
    • 5、强引用、软引用、弱引用和虚引用
    • 6、总结
  • 二、GC Algorithms(GC常用垃圾清除算法)
    • 1、mark sweep
    • 2、copying
    • 3、mark compact标记-压缩
  • 三、堆内存逻辑分区
    • 1、部分垃圾回收器使用的模型
    • 2、java heap 模型
    • 3、一个对象从出生到消亡
    • 4、专业名词:YGC/FGC
  • 四、栈上分配和TLAB(不少对象放的位置)
    • 1、栈上分配
    • 2、TLAB(Thread Local Allocation Buffer)
    • 3、程序测试(栈上分配和TLAB)
      • a、代码
      • b、结果分析
    • 4、总结
    • 5、对象内存分配的两种方法
      • a、指针碰撞
      • b、空闲列表
  • 五、对象何时进入老年代
    • 1、各种GC算法
    • 2、整体流程图
  • 六、有关老年代、新生代两个问题
    • 1、java 自带命令查找
    • 2、动态年龄:(不重要)
    • 3、分配担保:(不重要)
  • 七、常见的垃圾回收器(背)
    • 1、概述
    • 2、三种常见组合
    • 3、垃圾回收器详细介绍(==听的有点费劲==)
      • a、Serial GC回收器
      • b、PS(Parallel Scavenge)
      • c、ParNew(Parallel New)
      • d、Serial Old
      • e、Parallel Old
      • f、CMS(Concurrent Mark Sweep)(******)
        • i、概述
        • ii、CMS的问题
        • iii、CMS缺点(也就是上面的问题)
        • iii、CMS日志分析
      • g、G1
      • h、ZGC
      • i、Shenandoah
      • j、Eplison
      • k、PS 和 PN区别的延伸阅读
      • l、垃圾收集器跟内存大小的关系
  • 八、常见垃圾回收器组合参数设定

前言

  1. 本博文主要讲 invoke 指令、常用GC垃圾清除算法、堆内存逻辑分区、栈上分配、。
  2. Java虚拟机基本结构

一、GC(Garbage Collector)Tuning 垃圾回收器

1、什么是垃圾

垃圾: 没有引用指向的任何对象,都叫做垃圾。

2、java与C++的区别

  • java
    • GC处理垃圾
    • 开发效率高,执行效率低
  • C++
    • 手工处理垃圾
    • 忘记回收
      • 内存泄漏
    • 回收多次
      • 非法访问
    • 开发效率低,执行效率高

3、how to find a garbage?

标题:如何找到垃圾?

  • 两个方法的资料 https://cloud.tencent.com/developer/article/1656844
  • 一般有两种方法
    1. reference count 引用计数法
    2. root searching 根可达算法

a、reference count 引用计数法(java不用)

  • 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器就减1。任何时刻计数器为0的对象就是不再被使用的。
  • 无法解决的一种问题:循环引用。引用计数法其实是很难解决对象之间相互循环引用的问题,所以,Java虚拟机里面没有选用引用计数算法来管理内存。
  • Python 用的就是引用计数,但是怎么解决循环引用的,自行探索
  • 循环引用:如下图,三个对象互相引用,各自的计数器为1,但是没有其他对象引用这个循环引用,所以这是个垃圾,所以引用计数法无法解决这个问题。
    JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第1张图片

b、Root Searching 根可达算法(java用)

  • 在主流商用程序语言的主流实现中,都是称通过可达性分析来判定对象是否存活的
  • 算法的基本思路就是通过一系列称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用的。(说明根是一系列的对象)
  • 一系列的 GC roots 的对象,如下所示:
    1. JVM stack
      • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    2. static references in method area
      • 方法区中类静态属性引用的对象。
    3. run-time constant pool
      • 方法区中常量引用的对象。
    4. native method stack
      • 本地方法栈中JNI(即一般说的Native方法)引用的对象。
        JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第2张图片

4、引用的两次标记过程

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

5、强引用、软引用、弱引用和虚引用

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

6、总结

  • 简单的对上面做一个总结,在JVM中判断一个对象是都需要回收有两种算法:引用计数法和可达性算法。引用计数法是通过判断引用的计数器的值是否为0来确认回收与否。这种算法听起来很简单,但是存在一个缺陷,就可以可能存在循环引用的情况。

  • 还有一种就是可达性算法,可达性算法是通过判断引用能够被 GC Roots 访问到来确认回收与否。能被称为GC Roots对象也是有条件的主要有四种:虚拟机栈中引用的对象、方法中类静态属性引用的对象、方法中常量引用的对象和本地方法栈(native方法)中JNI引用的对象。

  • 引用分为四种类型:强引用、软引用、弱引用和虚引用。

二、GC Algorithms(GC常用垃圾清除算法)

  1. 标记清除(mark sweep)
    • 位置不连续
    • 产生碎片
    • 效率偏低(两遍扫描)
  2. 拷贝算法 (copying)
    • 没有碎片,浪费空间
  3. 标记压缩(mark compact)
    • 没有碎片,效率偏低(两遍扫描,指针需要调整)
  • 资料:GC垃圾回收算法

1、mark sweep

  • 优点
    • 算法相对简单,存活对象比较多的情况下效率较高
    • 所以不适合Eden(伊甸园区),因为伊甸园区的存活对象不多。
  • 缺点
    • 两遍扫描,效率偏低,容易产生碎片

2、copying

  • 优点
    • 适用于存活对象较少的情况,只扫描一次,效率提高,没有碎片
    • 适合Eden区。
  • 缺点
    • 空间浪费
    • 移动复制对象,需要调整对象引用 (所以使用句柄定位法中的变量是不用变的,只需要变句柄池中改变指针即可;具柄池和直接指针 在 JVM知识体系学习四第六章中:https://blog.csdn.net/qq_40036754/article/details/128555627)。

3、mark compact标记-压缩


  • 优点
    • 不会产生碎片,方便对象分配
    • 不会产生内存减半
  • 缺点
    • 扫描两次
    • 需要移动对象,效率偏低

三、堆内存逻辑分区

1、部分垃圾回收器使用的模型

  • 除Epsilon、ZGC、 Shenandoah之外的GC都是使用逻辑分代模型
  • G1是逻辑分代,物理不分代
  • 除此之外不仅逻辑分代,而且物理分代

2、java heap 模型

JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第3张图片

  • 上图中解释如下:

    1. new 区,也叫young 区,也叫新生代
    2. old 区,也就是老年代,也叫 tenured 区,(新生代:老年代=1:3)
    3. 新生代 = Eden + 2个suvivor区 (也称 from 、to)(比例也是8:1:1)
    4. 图中可以看出
      • 新生代 采用复制GC算法。
      • 老年代 采用 标记清楚 or 标记压缩 GC算法。
  • 新生代 + 老年代(这两个是在heap中) + 永久代(1.7)Perm Generation(永久代实现的方法区) / 元数据区(1.8) Metaspace 取代 永久代 实现 方法区 资料:(https://www.cnblogs.com/xiaofuge/p/14244755.html)

    可以说永久代或者元空间等同于方法区,不能说方法区等同于永久代。
    方法区是JVM的规范,而永久代是jdk1.8以前Hotspot对于方法区的实现。在jdk1.7以前,字符串常量池就保存在里面。1.7以后提出了去永久代的概念,第一步做的就是将字符串常量池移到了堆中。
    jdk1.8以后,移除永久代,在本地内存上开辟了一块空间,称为元空间,里面存放运行时常量池,class文件在jvm里的运行时数据结构,各种元数据等等。

    1. 永久代 和 元数据区:存的是Class数据
    2. 永久代必须指定大小限制 ,**元数据**可以设置,也可以不设置,无上限(受限于物理内存)
    3. 字符串常量, 1.7 在 永久代,1.8 在 堆中
    4. MethodArea逻辑概念 - 永久代、元数据区
  • 新生代 = Eden + 2个suvivor区

    1. YGC回收之后,大多数的对象会被回收,活着的进入s0
    2. 再次YGC,活着的对象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年龄足够 -> 老年代 (15 CMS 6)
    5. s区装不下 -> 老年代
  • 老年代

    1. 顽固分子
    2. 老年代满了FGC Full GC
  • GC Tuning (Generation)

    1. 尽量减少FGC
    2. MinorGC = YGC
    3. MajorGC = FGC

3、一个对象从出生到消亡

JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第4张图片

  1. 一个对象产生之后,首先尝试栈上进行分配。
  2. 栈上分配如果分配不下,就进行Eden区
  3. Eden区经过一次垃圾回收之后, 进入 S1区(survive区)。
  4. S1区再经过一次垃圾回收机制之后,就进入S2区。
  5. 在S2和S1区之间来回经历,然后经过很老之后(年龄)就进入了老年代。
  • S1-S2 之间的复制年龄超过限制时,进入old区通过参数:-XX:MaxTenuringThreshold 配置。

4、专业名词:YGC/FGC

从图中可以看出:

  1. MainorGC 即 YGC:年轻代空间耗尽时触发。
  2. MajorGC 即 FullGC/FGC :在老年代无法继续分配空间时触发,新生代老年代同时进行回收。

四、栈上分配和TLAB(不少对象放的位置)

  • 面试中可能会问:对象都会分配到 java heap 上嘛?
  • 答案肯定是否定的。那除了分配到 heap 上,还会分配到哪里呢?
    • 答:大多数对象会分配到 java heap 上,但是还有一些对象比较例外,如果都放到 java heap 上,会引起效率低下;所以还会放到 stack 和 TLAB(Thread Local Allocation Buffer,即线程本地分配缓存区)上
    • 为什么会效率低下呢?有些对象是线程私有的,在方法内部产生使用,并没有去到外部,这种对象就随着方法或者线程结束而消失;所以这种对象就没有必要放在java heap 中,放在 stack 或者TLAB上就好。
    • 详细资料一:Java常见面试题—栈分配与TLAB
    • 详细资料二:Java对象栈上分配
  • 继续问:什么样的内容会分配到栈上呢?什么样的内容会继续往Eden区分配?

1、栈上分配

  • 题外话:(有对象放在了 java stack 上,也就回答了 在判定对象是否存活的·根可达算法上的 GC root 上,为啥会有个根在 java stack 上了,因为有对象放在线程私有的 java stack 上 )。

  • 在JVM中,堆是线程共享的,因此堆上的对象对于各个线程都是共享和可见的,只要持有对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但对于垃圾收集器来说,无论筛选可回收对象,还是回收和整理内存都需要耗费时间。

  • 如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载。

  • JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。

  • 栈上分配的技术基础:

    • 一是 逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。关于逃逸分析的问题 请看资料:Java中的逃逸分析
    • 二是 标量替换:允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
  • 只能在server模式下才能启用逃逸分析,

    • 参数 -XX:DoEscapeAnalysis 启用逃逸分析,
    • 参数 -XX:+EliminateAllocations开启标量替换(默认打开)。
    • Java SE 6u23版本之后,HotSpot中默认就开启了逃逸分析,可以通过选项 -XX:+PrintEscapeAnalysis 查看逃逸分析的筛选结果。

2、TLAB(Thread Local Allocation Buffer)

  • TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。

  • 由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。

  • TLAB本身占用eEden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

  • 由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。

    • 比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,
      • 第一,废弃当前TLAB,这样就会浪费20KB空间;
      • 第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。
    • 实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于 refill_waste 时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction 来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
  • -XX:+PrintTLAB 可以跟踪TLAB的使用情况。一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为。

3、程序测试(栈上分配和TLAB)

a、代码

package com.mashibing.jvm.c5_gc;

//-XX:-DoEscapeAnalysis(去掉逃逸分析) -XX:-EliminateAllocations(去掉标量替换) -XX:-UseTLAB(去掉TLAB) -Xlog:c5_gc*
// 逃逸分析 标量替换 线程专有对象分配

public class TestTLAB {
    //User u;
    class User {
        int id;
        String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    void alloc(int i) {
        new User(i, "name " + i);
    }

    public static void main(String[] args) {
        TestTLAB t = new TestTLAB();
        long start = System.currentTimeMillis();
        for(int i=0; i<1000_0000; i++) t.alloc(i);
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        //for(;;);
    }
}

b、结果分析

  • 虚拟机默认开启 栈上分配(逃逸分析、标量替换)、TLAB 的,在方法里创建了1000W个对象所需时间如下:
    JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第5张图片
  • 然后关掉 栈上分配、TLAB:
    1. -XX:-DoEscapeAnalysis:(去掉逃逸分析)
    2. -XX:-EliminateAllocations:(去掉标量替换)
    3. -XX:-UseTLAB:(去掉TLAB)
      JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第6张图片
    4. 结果如下:时间会变长。
      JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第7张图片

4、总结

  • 栈上分配

    1. 线程私有小对象:放在栈针的局部变量表中
    2. 无逃逸
    3. 支持 逃逸分析、标量替换
    4. 无需调整:虚拟机默认设置好了
  • 线程本地分配TLAB (Thread Local Allocation Buffer)

    1. 占用eden,默认1%
    2. 多线程的时候不用竞争eden就可以申请空间,提高效率
    3. 小对象
    4. 无需调整:虚拟机默认设置好了
  • 老年代

    • 大对象

5、对象内存分配的两种方法

  • 这部分内容,按说应该放在对象部分,但是作为补充就放在这里了

  • 为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

a、指针碰撞

  • 指针碰撞(Serial、ParNew等带Compact过程的收集器)
    • 假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。

b、空闲列表

  • 空闲列表(CMS这种基于Mark-Sweep算法的收集器)
    • 如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。

五、对象何时进入老年代

1、各种GC算法

  • 超过 XX:MaxTenuringThreshold 指定次数(YGC)进入老年代
    1. `Parallel Scavenge 15(最大就是15,在讲JMM的时候,对象头的GC age 就是4位,最大就是15,不能调整了
    2. CMS 6
    3. G1 15
  • 动态年龄
    1. s1 - > s2超过50%
    2. 把年龄最大的放入O
      JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第8张图片

2、整体流程图

JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第9张图片

  1. 略过栈上分配。(简单)
  2. 对象足够大的时候,直接进入 old 区,通过FGC回收
  3. 如果不大,进入TLAB区,最终进入Eden区
  4. 进行判断,是否清除,如果清除则直接回收
  5. 否则进入S1区,判断年龄是否到位了,如果够了直接进入old区,否则进入S2区,然后判断是否应该清除 进入S1区循环。

六、有关老年代、新生代两个问题

1、java 自带命令查找

JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第10张图片

JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第11张图片

  1. -开头 是标准参数
  2. -X开头 是非标准参数
  3. -XX开头 是非标不稳定参数(非标就是非标准版本)
    JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第12张图片

2、动态年龄:(不重要)

https://www.jianshu.com/p/989d3b06a49d

3、分配担保:(不重要)

  • YGC期间 survivor 区空间不够了 空间担保直接进入老年代
  • 参考:https://cloud.tencent.com/developer/article/1082730

七、常见的垃圾回收器(背)

1、概述


(右上角那个 Epsilon 是debug用的,不用管。)

  • 上图解释

    1. 左边的六个,都是逻辑分代,物理也分代;上面三个是 新生代的垃圾回收器,下面三个是老年代的垃圾回收器。
    2. 右边的G1 是 逻辑分代,物理不分代。
    3. ZFC、Shenandoah 逻辑、物理都不分代。
    4. 所有的垃圾回收器 都是 STW(stop the Word)。
  • 1.8默认的垃圾回收:PS + ParallelOld

    • 不光是逻辑上分新生代、老年代;物理上也分新生代和老年代。
    • jvm 1.8 默认的垃圾回收器
  • 资料:

    • 深入理解 JVM 垃圾回收机制及其实现原理
    • [JVM基础-- 垃圾回收器

2、三种常见组合

从上图连线中可以看出:

  1. Serial(单线程)+Serial Old
  2. Parallel Scavenge(多线程) + Parallel Old
  3. ParNew + CMS
  4. 其他虚线的都是不常见组合

3、垃圾回收器详细介绍(听的有点费劲

  1. JDK诞生 Serial追随 提高效率,诞生了PS,
    • 为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前没有任何一个JDK版本默认是CMS并发垃圾回收(CMS)是因为无法忍受STW。
    • G1是1.7引入的,1.8稳定使用

a、Serial GC回收器

  1. Serial 年轻代串行回收
    • a stop-the-world (STW,使用这个属于), copying collector which uses a single GC thread。
    • 单CPU效率最高,虚拟机是Client模式的默认垃圾回收器
    • safe point 。
    • 用的极少。
    • 如下图形象展示:JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第13张图片

b、PS(Parallel Scavenge)

PS(Parallel Scavenge),年轻代,并行回收 。

  1. 虚拟机默认的就是 PS+PO 组合垃圾回收器。

c、ParNew(Parallel New)

ParNew,年轻代 配合CMS的并行回收

  1. 对 PS 的增强,
  2. 和CMS组合使用

d、Serial Old

Serial Old

e、Parallel Old

PS(Parallel Old)年轻代,并行回收
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第14张图片

f、CMS(Concurrent Mark Sweep)(******)

前后古人的新算法。

i、概述

  • CMS(Concurrent(并发) Mark Sweep),1.4版本后期引入, 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)。

  • CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定

  • 从线程角度来看,有四个阶段(有的地方写6个阶段)如图所示
    JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS_第15张图片

    1. initial mark,初试标记:STW,标记根上的,时间较短
    2. concurrent mark,并发标记:和并发线程同时标记
    3. remark:重新标记:STW,标记产生新的垃圾,所以需要stop,但是新产生垃圾不多,所以时间也很快
    4. concurrent sweep:并发清理:最后清理,产生的问题就是 ,同时也会产生新的垃圾,这种就是浮动垃圾。只能等下一次运行才能清掉。
  • CMS既然是采用 Mark-Sweep(标记-清除)算法,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收。

  • 想象一下:

    • PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
    • 几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
    • 算法:三色标记 + Incremental Update

ii、CMS的问题

  1. Memory Fragmentation(内存碎片 )

-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

  1. Floating Garbage(浮动垃圾)

Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
解决方案:降低触发CMS的阈值
PromotionFailed
解决方案类似,保持老年代有足够的空间
–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

iii、CMS缺点(也就是上面的问题)

  • memory fragmentation
    • -XX:CMSFullGCsBeforeCompaction
  • floating garbage
    • Concurrent Mode Failure –XX:CMSInitiatingOccupancyFraction 92%
    • SerialOld

iii、CMS日志分析

执行命令:

java -Xms20M -Xmx20M
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01

GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs

;ParNew:年轻代收集器
6144->640:收集前后的对比
(6144):整个年轻代容量
6585 -> 2770:整个堆的情况
(19840):整个堆大小

g、G1

G1(10ms)
算法:三色标记 + SATB

h、ZGC

ZGC (1ms) PK C++
算法:ColoredPointers + LoadBarrier

i、Shenandoah

Shenandoah
算法:ColoredPointers + WriteBarrier

j、Eplison

Eplison,debug 使用。

k、PS 和 PN区别的延伸阅读

  • https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73

l、垃圾收集器跟内存大小的关系

  1. Serial 几十兆
  2. PS 上百兆 - 几个G
  3. CMS - 20G
  4. G1 - 上百G
  5. ZGC - 4T - 16T(JDK13)

八、常见垃圾回收器组合参数设定

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld
    • 这个组合已经很少用(在某些版本中已经废弃)
    • https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
  • -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
  • -XX:+UseG1GC = G1
  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
    • java +XX:+PrintCommandLineFlags -version
    • 通过GC的日志来分辨
  • Linux下1.8版本默认的垃圾回收器到底是什么?
    • 1.8.0_181 默认(看不出来)Copy MarkCompact
    • 1.8.0_222 默认 PS + PO

你可能感兴趣的:(JVM,jvm,算法,学习)