IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> G1 垃圾回收器 -> 正文阅读

[Java知识库]G1 垃圾回收器

一、垃圾收集器简介

1、发展历程

  • 第一阶段,Serial(串行)收集器
    在jdk1.3.1之前,java虚拟机仅仅能使用Serial收集器。 Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

  • 第二阶段,Parallel(并行)收集器
    Parallel收集器也称吞吐量收集器,相比Serial收集器,Parallel最主要的优势在于使用多线程去完成垃圾清理工作,这样可以充分利用多核的特性,大幅降低gc时间。

  • 第三阶段,CMS(并发)收集器
    CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。

  • 第四阶段,G1(并发)收集器
    G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。

2、种类

  • 新生代

    • Serial (第一代)
    • PraNew (第二代)
    • Parallel Scavenge (第三代)
    • G1收集器(第四代)
  • 老年代

    • Serial Old (第一代)
    • Parallel Old (第二代)
    • CMS (第三代)
    • G1

二、G1 介绍

1、概述

  1. G1收集器的最大特点
    • G1最大的特点是引入分区的思路,弱化了分代的概念
    • 合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷
  2. G1相比较CMS的改进
    • 算法:基于标记-整理算法,不会产生空间碎片,分配大对象时不会因得不到连续空间而提前触发 FULL GC
    • 停顿时间可控: G1可以通过设置预期停顿时间 Pause Time 来控制垃圾收集时间避免应用雪崩现象
    • 并行与并发:G1 能充分利用 多核 CPU 的硬件优势来缩短 stop the world 的停顿时间
  3. CMS和G1的区别
    • CMS 的堆分为 PermGen、YoungGen、OldGen;而YoungGen又分了两个survivo区域
      G1 的堆被分为区域(region),每个区域虽然保留了新老代概念,但收集器是以整个区域为单位收集
    • G1在回收内存后会马上同时做合并空闲内存的工作、而 CMS 默认是在STW(stop the world)时做
    • G1会在 Young GC 中使用、而 CMS 只能在 O 区使用
  4. G1收集器的应用场景
    • G1垃圾收集算法主要应用在多CPU大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾回收时的暂停时间。
    • 就目前而言、CMS还是默认首选的GC策略、可能在以下场景下G1更适合:
      • 服务端多核CPU、JVM内存占用较大的应用(至少大于4G)
      • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间
      • 想要更可控、可预期的GC停顿周期,防止高并发下应用雪崩现象

2、G1的堆内存算法

G1之前的JVM内存模型
在这里插入图片描述


G1收集器的内存模型
在这里插入图片描述

  • G1 收集器将整个Java 堆划分成约 2048 个大小相同的独立 Region 块,每个Region块大小根据堆空间的实际大小而定

    可以通过 -XX :G1HeapRegionSize 设定,值在 1MB~32MB 之间且为 2 的次幂

  • region 可能属于 Eden、Survivor、0ld、Humongous 区域,但一个region只可能属于一个角色

  • Humongous 作用:用来专门存放大对象,一般使用连续 region 区存储,G1的大多数行为都把H区
    作为老年代的一部分来看待

3、G1 的特点、缺点

3.1 特点

①. 并行和并发

  • 并行性:G1 在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力
  • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行

②. 分代收集

  • 从分代上看,G1 仍属于分代型垃圾回收器,会区分年轻代和老年代,年轻代依然有 Eden 区和 Survivor 区
    但从堆的结构上看,不要求整个 Eden 区、年轻代或老年代都连续,也不再坚持固定大小和固定数量
  • 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代
  • 和之前的各类回收器不同,其同时兼顾年轻代和老年代

③. 空间整合

  • 内存回收以 region 为基本单位,Region之间是复制算法,但整体是标记-压缩算法,可以避免内存碎片,有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC

④. 可预测的停顿时间模型(即:软实时soft real一time)
可预测的停顿时间模型:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒,通过参数 -XX:MaxGCPauseMillis 设置

  • 由于分区的原因,G1可以只选取部分区域进行内存回收,缩小回收范围,避免全局停顿情况的发生
  • G1 跟踪各个 Region 中垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
  • 相比于 CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但最差情况要好很多

3.2 缺点

  • 相较于CMS,G1 还不具备全方位、压倒性优势,如:G1 无论是为了垃圾收集产生的内存占用,还是程序运行时的额外执行负载都要比 CMS 高
  • 小内存应用上,CMS 的表现大概率会优于 G1,而 G1 在大内存应用上则发挥其优势,平衡点在6-8GB之间

4、G1 独有概念

4.1 RSet 与 card

RSet 与 card 专门用来处理 Old 区到 Young 区的引用
Young 区到 Old 区的引用不需要单独处理,因为 Young 区中的对象本身变化比较大,没必要浪费空间去记录下来

  • RSet:用来记录外部指向本 Region 的所有引用,每个 Region 维护一个 RSet
  • Card: JVM 将内存划分成了固定大小的 Card,类比物理内存上 page 的概念
    在这里插入图片描述
  • 每个 Region 分成多个 Card,其中绿色部分的 Card 表示该 Card 中有对象引用了其他 Card 中的对象,这种引用关系用蓝色实线表示。
  • RSet 其实是一个HashTable,Key 是 Region 的起始地址,Value 是Card Table(字节数组),字节数组下标表示 Card 的空间地址,当该地址空间被引用时会被标记为 dirty_card

RSet 的更新

G1 采用 post-write barrierconcurrent refinement threads 更新 RSet,减少每次给引用类型的字段赋值都要更新 RSet带来的开销

java 层面给 old 对象的 p 字段赋值 young 对象之后,jvm 底层会执行 oop_store 方法

在赋值动作的前后,JVM插入一个 pre-write barrierpost-write barrier

post-write barrier 的最终动作如下:

  • 找到该字段所在的位置(Card),并设置为 dirty_card
  • 若当前是应用线程,每个Java线程有一个 dirty card queue,把该 card 插入队列
  • 除了每个线程自带的 dirty card queue,还有一个全局共享的 queue

RSet 更新操作交由多个 ConcurrentG1RefineThread 并发完成,每当全局队列集合超过一定阈值后,ConcurrentG1RefineThread 会取出若干个队列,遍历每个队列中记录的card,并进行处理,大概实现逻辑如下:
1、根据 card 的地址,计算出 card 所在的 Region
2、若 Region 不存在,或 Region 是 Young 区,或该 Region 在回收集合中,则不进行处理
3、最终使用闭合函数 G1UpdateRSOrPushRefOopClosure::do_oop_nv() 的处理该 card 中的对象

refinement threads 线程数量可以通过 -XX:G1ConcRefinementThreads-XX:ParallelGCThreads 参数设置

4.2 Collection Set

收集集合(CSet):一组可被回收的分区集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、survivor空间、老年代

GC时在CSet中的所有存活数据都会被转移,分区释放回空闲分区队列

4.3 PLAB

在 GC 线程的晋升本地分配缓冲区(PLAB)中,对象晋升到 survivor 分区或老年代分区

每个线程有独立的PLAB,作用是避免多线程竞争相同数据

4.4 TLAB

JVM使用线程本地分配缓存TLAB 这种线程专属的区间,来避免多线程冲突(无锁方式),提高对象分配效率

TLAB 本身占用了 Eden 空间,即 JVM 会为每一个线程都分配一块 TLAB 空间

在这里插入图片描述

5、G1 回收过程

大致流程

  1. 当 Eden 区用尽时,开始年轻代回收,而 G1 的年轻代收集阶段是一个并行(多个垃圾线程)的独占式收集器

    年轻代回收时,G1 暂停所有应用程序线程,启动多线程执行年轻代回收,然后移动存活对象到Survivor 或 O 区

  2. 当堆内存使用达到一定值(默认45%,-XX:InitiatingHeapOccupancyPercent)时,开始老年代并发标记过程
  3. 标记完成马上开始混合回收过程,此时 G1 从 O 区移动存活对象到空闲区间

    一次只需要扫描/回收一小部分老年代的 Region 就可以

  4. 若 GC 的评估失败,则提供失败保护机制,即强力回收 FULL GC(单线程、独占式、高强度)

在这里插入图片描述

5.1 Young GC

  • 回收时机:当 Eden 空间耗尽时,G1会启动一次年轻代垃圾回收过程
  • 回收对象:年轻代垃圾回收只会回收 Eden 区和 Survivor 区

回收流程

  • 第一阶段,根扫描,根引用连同 RSet 记录的外部引用作为扫描存活对象的入口
    • 根是指 static 变量指向的对象,正在执行的方法调用链条上的局部变量等
    • 扫描 remembered Set,看是否有老年代中的对象引用了新生代对象
  • 第二阶段,更新 RSet
    • 处理 dirty card queue 中的 card,更新 RSet 后,可以准确反映老年代对所在的内存分段中对象的引用
  • 第三阶段,处理 RSet
    • 识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象
  • 第四阶段,复制对象,遍历对象树
    • Eden 区内存段中存活的对象会被复制到 Survivor 区中空的内存分段
    • Survivor 区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被复制到 old 区中空的内存分段
    • 若 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间
  • 第五阶段,处理引用,处理 Soft、Weak、Phantom、Final、JNI Weak 等引用
    • 最终 Eden 空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片

5.2 Concurrent Marking

  1. 初始标记阶段:标记从根节点直接可达的对象,这个阶段是 STW 的,并且会触发一次年轻代 GC

  2. 根区域扫描(Root Region Scanning):G1 GC 扫描 Survivor 区直接可达的老年代区域对象,并标记被引用的对象

    这一过程必须在 young GC 之前完成(YoungGC时,会动 Survivor 区,所以这一过程必须在young GC之前完成)

  3. 并发标记(Concurrent Marking):在并发标记阶段,若发现区域对象中的所有对象都是垃圾,则这个区域会被立即回收,此过程可能被 young GC 中断

    并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)

  4. 再次标记(Remark):修正上一次的标记结果,是 STW

  5. 独占清理(cleanup,STW):计算各个区域的存活对象和 GC 回收比例,并进行排序,识别可以混合回收的区域,是STW 的

    这个阶段并不会实际上去做垃圾的收集

  6. 并发清理阶段:识别并清理完全空闲的区域

5.3 Mixed GC

  • 触发时机:老年代的堆占有率达到参数 -XX:InitiatingHeapOccupancyPercent 设定的值则触发
  • 回收对象:回收所有的 Young 和部分 Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区
  • 回收过程:MixedGC 主要使用复制算法,把各个 region 中存活的对象拷贝到别的 region 里去,拷贝过程中若发现没有足够的空 region 能够承载拷贝对象就会触发一次 Full GC

  • 并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGCCountTarget设置)被回收
  • 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区 内存分段,Survivor区内存分段。 混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考上面的年轻代回收过程。
  • 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间
  • 混合回收并不一定 要进行8次。有一个阈值**-XX :G1HeapWastePercent**,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少

5.4 Full GC

  • 特点:G1会停止应用程序的执行(Stop-The-World) ,使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
  • 导致 G1 Full GC 的原因可能有两个: .
    • 回收时,没有足够的 to-space 来存放晋升对象
    • 并发处理过程没完成空间就耗尽
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:22:50  更:2022-03-22 20:26:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:58:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码