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

简介: 这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。

前言

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

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

1、什么是垃圾

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

2、java与C++的区别

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

3、how to find a garbage?

标题:如何找到垃圾?

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

  • 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器就减1。任何时刻计数器为0的对象就是不再被使用的。
  • 无法解决的一种问题:循环引用。引用计数法其实是很难解决对象之间相互循环引用的问题,所以,Java虚拟机里面没有选用引用计数算法来管理内存。
  • Python 用的就是引用计数,但是怎么解决循环引用的,自行探索
  • 循环引用:如下图,三个对象互相引用,各自的计数器为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方法)引用的对象。
    5. 堆应该也是一个 GC roots 的对象,但是我不知道为什么网上其他的文章几乎都没有写 堆的,大量的对象都在堆上,怎么可能不应该写呢。这里记录一下(20230225)
      在这里插入图片描述

4、引用的两次标记过程

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

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

6、总结

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

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

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

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

  1. 标记清除(mark sweep)
    • 位置不连续
    • 缺点:产生碎片、效率偏低(两遍扫描)、不适合Eden区(因为该区存活对象不多)。
  2. 拷贝算法 (copying)
    • 没有碎片,浪费空间
    • 缺点:移动复制对象,需要调整对象引用。
  3. 标记压缩(mark compact)
    • 没有碎片,
    • 缺点:效率偏低(两遍扫描,指针需要调整)

1、mark sweep

在这里插入图片描述

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

2、copying

在这里插入图片描述

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

3、mark compact标记-压缩

在这里插入图片描述
在这里插入图片描述

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

三、堆内存逻辑分区

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

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

2、java heap 模型

在这里插入图片描述

  • 上图中解释如下:

    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以后,移除永久代,在本地内存上开辟了一块空间,称为元空间,里面存放运行时常量池、6个基本数据包装类常量池,class文件在jvm里的运行时数据结构,各种元数据等等,将静态变量移到了堆中。

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

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

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

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

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

在这里插入图片描述

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

4、专业名词:YGC/FGC

从图中可以看出:

  1. MainorGC/YoungerGC 即 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个对象所需时间如下:
    在这里插入图片描述
  • 然后关掉 栈上分配、TLAB:
    1. -XX:-DoEscapeAnalysis:(去掉逃逸分析)
    2. -XX:-EliminateAllocations:(去掉标量替换)
    3. -XX:-UseTLAB:(去掉TLAB)
      在这里插入图片描述
    4. 结果如下:时间会变长。
      在这里插入图片描述

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
      在这里插入图片描述

2、整体流程图

在这里插入图片描述

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

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

1、java 自带命令查找

在这里插入图片描述

在这里插入图片描述

  1. -开头 是标准参数
  2. -X开头 是非标准参数
  3. -XX开头 是非标不稳定参数(非标就是非标准版本)
    吧

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

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

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

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

1、概述

在这里插入图片描述
(右上角那个 Epsilon 是debug用的,不用管。)

  • 上图解释

    1. 左边的六个,都是逻辑分代,物理也分代;上面三个是 新生代的垃圾回收器,下面三个是老年代的垃圾回收器。
    2. 右边的G1 是 逻辑分代,物理不分代。
    3. ZFC、Shenandoah 逻辑、物理都不分代。
    4. 所有的垃圾回收器 都是 STW(stop the Word)。
  • Tips通过名字就可以看出来,并行 的都带有Parallel关键字,ParNew的Par也是Parallel缩写。

  • 1.8默认的垃圾回收:PS(ParallelScavenge) + ParallelOld

  • 资料:

2、三种常见组合

从上图连线中可以看出:

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

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

  • 垃圾回收作用在堆上的分代模型上(新生代YGC、老年代FGC)。

    • 1、串行回收:一个回收线程到屋子(项目)里去回收,回收时项目需要STW。
    • 2、并行回收:多个回收线程到屋子(项目)里去回收,回收时项目也需要STW
    • CMS(Concurrent Mark Sweep):并发回收,是一边运行一边回收,但是问题较多,目前没有一个JDK版本默认使用CMS。也正是它的出现,才诞生了G1、ZGC。
      • 注意:并发回收 和 并行回收不是一回事儿。jVM串行、并行、并发垃圾回收器
      • 并行回收 是多个垃圾线程去干活,但是会有STW。同一时刻,要么用户线程要么垃圾回收线程 且 垃圾回收是 多线程
      • 3、并发回收 是多个垃圾线程和工作线程同时干活,没有STW。同一时刻,用户线程 和 垃圾回收线程 可能同时执行。
      • 两个缺点:碎片化导致FGC和浮动垃圾。所以使用CMS+PN 要避免FGC,但是避免不了,无办法。
  • JDK诞生 Serial;随着内存变大,Serial满足不了效率了,为了追随 提高效率,诞生了PS。

    • 为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它 开启了并发回收的过程 ,但是CMS毛病较多,因此目前没有任何一个JDK版本默认是CMS并发垃圾回收(CMS)是因为无法忍受STW。
    • G1是1.7引入的,9稳定使用
  • 垃圾回收器回收垃圾时,会全部扫描整个内存,当内存太大的时候,STW时间也会更久,这是项目所不能允许的,所以才会诞生CMS

a、Serial GC(年轻)

  • Serial,年轻代,串行回收
  • a stop-the-world (STW,使用这个属于), copying collector which uses a single GC thread。
  • 单CPU效率最高,虚拟机是Client模式的默认垃圾回收器
  • safe point :安全点。
  • 用的极少
  • 如下图形象展示:在这里插入图片描述

b、PS(Parallel Scavenge)(年轻)

  • PS(Parallel Scavenge),年轻代,并行回收,JDK1.8 默认使用。
  • 虚拟机1.8默认的就是 PS+PO 组合垃圾回收器。

c、ParNew(Parallel New)(年轻)

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

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

d、Serial Old(老年)

  • Serial Old,老年代,很老的回收器,串行。
  • 之前内存小,一个串行的可以解决

e、Parallel Old(老年)

PS(Parallel Old,老年代,并行回收
在这里插入图片描述

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

  • 前后古人的新算法,老年代。
  • 开启了并发回收的过程,正是它的出现,才诞生了G1、ZGC
i、CMS概述
  • CMS(Concurrent(并发) Mark Sweep),1.4版本后期引入, 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)。
  • 一边运行一边垃圾回收。
ii、四个阶段
  • CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定

  • 从线程角度来看,有四个阶段 (有的地方写6个阶段)如图所示
    在这里插入图片描述

    1. initial mark,初始标记:先标记根上的(不多),这是STW的,时间非常短
      在这里插入图片描述

    2. concurrent mark并发标记(最耗时阶段):和 并发线程 同时标记,无STW。但是在找的过程(并发过程),有可能发现有的垃圾此时被引用了,此时就不是垃圾了,此时进入第三阶段:remark阶段。
      在这里插入图片描述

    3. remark,重新标记:STW,标记产生新的垃圾,所以需要stop,但是新产生垃圾不多,所以时间也很快。
      在这里插入图片描述

    4. concurrent sweep并发清理:最后清理。产生的问题就是 ,同时也会产生新的垃圾,这种就是 浮动垃圾(下面线程角度中也有讲) 。只能等下一次运行才能清掉。
      在这里插入图片描述

    5. 当看到第2步和第3步的时候,在JVM里看到Concurrent,说明垃圾回收线程和工作线程在一块儿工作,是这个意思

  • 线程角度:
    在下图中,可以看出,在第二次STW时,工作进程也在进行,肯定也会产生垃圾,这里的垃圾就叫 浮动垃圾。这种浮动垃圾 只能等下一次运行才能清掉。
    在这里插入图片描述

  • CMS既然是采用 Mark-Sweep(标记-清除)算法,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收。

  • 想象一下:

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

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

  2. 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保持老年代足够的空间

iv、CMS缺点(也就是上面的问题)
  • memory fragmentation
    • -XX:CMSFullGCsBeforeCompaction
  • floating garbage
    • Concurrent Mode Failure –XX:CMSInitiatingOccupancyFraction 92%
    • SerialOld
v、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(JDK9 )

i、基础概述和常见问题
  • G1阐述入门:Garbage First Garbage Collector Tuning

    • The Garbage First Garbage Collector (G1 GC) is the lowpause, server-style generational garbage collector for Java HotSpot VM. The G1 GC uses concurrent(并发) and parallel(并行) phases to achieve its target pause time and to maintain good throughput(吞吐量). When G1 GC determines that a garbage collection is necessary, it collects the regions with the least live data first (garbage first G1的来源).
    • G1的吞吐量和PS相比,要降低了10%-15%,但是STW停顿时间一般是200ms。
      • 如果想让程序200ms都有响应用G1,
      • 如果应用程序追求throughput(吞吐量)则用PS。
    • G1是一种服务端应用使用的垃圾收集器,目标是用在多核、大内 存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同 时还能保持较高的吞吐量
  • G1(10ms)算法:三色标记 + SATB

  • 在分代算法中:采用逻辑分代,物理不分代。

  • 计算机设计架构中的两大思想:

    • 分而治之
    • 分层思想
  • 基本概念(不是很重要)

    1. card table
      • 黄色小球是一个个的对象,红色小球为GCroot的根对象,红色小球有可能引用指向到了老年代,而老年代有可能也指向了年轻代里。做一个YGC,有可能需要去遍历老年代里所有的对象,看有没有指向。所以很费劲。
      • 所以把内存分为一个个的card,则一个个对象都存在一个个的card中。
      • 由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card
      • 在结构上,Card Table用BitMap来实现 在这里插入图片描述
    2. CSet = Collection Set

      • G1根据内部的算法,有好多card,有哪些card需要被回收,这些被回收的card放到了一张表格里,这个表格就是Collection Set

      • 一组可被回收的分区的集合。

        • 在CSet中存活的数据会在GC过程中被移动到另一个可用分区,
        • CSet中的分区可以来自Eden空间、survivor空间、或者老年代。
        • CSet会占用不到整个堆空间的1%大小。
    3. RSet: RememberedSet,(ZGC 舍弃了,没有了)
      • 记录了 其他Region 中的对象到 本Region 的引用,
      • RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可
        在这里插入图片描述
  • 扩展:阿里的多租户JVM

    • 每租户单空间
    • session based GC
  • 新老年代的比例

    • 5%-60%,不需要调优啦
      • 一般不用手工指定
      • 也不要手工指定,因为这是G1预测停顿时间的基准
  • GC何时触发(有三种GC)

    • 产生YGC 的情况
      • Eden空间不足
      • 多线程并行执行
    • 产生MixedGC 的情况
      • 等价于 CMS ;YGC已经不行了,对象的产生特别多了,超过45%了。这时启动MixedGC。
    • 产生FGC 的情况
      • Old空间不足
      • System.gc()
  • 如果G1产生FGC,你应该做什么?

    1. 扩内存
    2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
    3. 降低 MixedGC 触发的阈值,让 MixedGC 提早发生(默认是45%)
  • G1中的MixedGC

    • 等价于 CMS ;YGC已经不行了,对象的产生特别多了,超过45%了。这时启动MixedGC。
    • -XX:InitiatingHeapOccupacyPercent
      • 默认值45%,可以自定义。
      • 当O超过这个值时,启动 MixedGC
    • MixedGC的过程:四步(和CMS的回收阶段大致一样)

      • 初始标记,标记根 STW
        在这里插入图片描述

      • 并发标记
        SATB算法:
        Snapshot At The BeginningGC开始时,通过root tracing得到一个Snapshot维持并发GC的正确性

        如何做到并发GC的正确性:
        三色标记算法:
        :对象没有标记,标记阶段结束后,会被回收
        :对象标记了,但是他的Field还没有标记或标记完
        :对象标记了,且他的Field也标记完成了 在这里插入图片描述

      • 最终标记 STW (重新标记)
        在这里插入图片描述

      • 筛选回收 STW (并行):
        和CMS的区别之处就是这里有筛选过程:筛选最需要回收的,垃圾占得最多的Region;将Region复制到另一块区域里,复制同时进行压缩。碎片也就没有CMS那么多。
        在这里插入图片描述

      • 并行筛选回收

  • G1的Full GC:java 10以前是串行FullGC,之后是并行FullGC。

    • G1调优目标之一:尽量减少FGC,
    • 扩展回忆上面知识:如果G1产生FGC,你应该做什么?
ii、G1的内存物理模型
  • G1的内存物理模型:将内存分为一小块的 Region(区域),对应逻辑分代上的 新生区(Eden)、存活区(Survivor)、老年区(Old)、大对象区(Humongous)。一般都是2n 次方大小。
    在这里插入图片描述

  • 每个 Region 有多大

    • headpRegion.cpp
    • 取值:1,2,4,8,16,32
    • 手工指定:-XX:G1HeapRegionSize
  • Humongous Object

    • 超过单个region的50%
      在这里插入图片描述
  • 每个分区都可能是年轻代也可能是老年代,但是在同一时
    刻只能属于某个代。

  • 年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前分代框架的逻辑。在物理上不需要连续,则带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先收集垃圾最多的分区。

  • 新生代其实并不是适用于这种算法的,依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。

  • G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等,但是都是2的冥次方。

iii、四大特点
  1. 并发收集
    • 颜色指针(color pointer): JVM中,class pointer(类指针) 没有压缩,是64位,拿出三位,做标记,当指向不同引用时,指针产生变化,指向A,又指向了B,在产生变化的过程中,用这三个标记位给标记出来,这个指针变过了;当垃圾回收时,会扫描变化过的指针,这就是color pointer。(不是这里的知识点)
    • 三色标记:把对象分为三个不同的颜色,每个不同的颜色标记着它到底有没有被标记过,标记了一半,还是完全没有被标记过。
  2. 压缩空闲空间不会延长GC的暂停时间;
  3. 更易预测的GC暂停时间(STW);
  4. 适用不需要实现很高的吞吐量的场景

h、ZGC (JDK11)

i、Shenandoah(JDK)

Shenandoah
算法:ColoredPointers + WriteBarrier

j、Eplison(debug 使用)

Eplison,debug 使用。

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

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
  • -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
相关文章
|
25天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
208 1
|
14天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
23天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
24天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
19 3
|
25天前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
45 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
2月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
36 4
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
58 2
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
67 3