2021-01-17 JVM -- G1垃圾收集器

引言:CMS缺陷:

  1. 单核、双核情况下,效率低。
  2. foreground 模式(并发失败导致)下垃圾回收占用时间较长,比如:可中止预处理 默认是5s, MSC - 内存整理(Mark-Sweep-Compate)以及切换为单线程垃圾收集器- Serial Old收集器执行都是非常耗时间的操作。而且是无法人为干预的。

G1 (Garbage-First)垃圾收集器

垃圾收集器关注点是:停顿时间和吞吐量。

垃圾收集器 停顿时间 吞吐量 是否并发
CMS 尽可能小,不能人为控制 基本无法控制
Parallel Scavenge 可控制 可控
G1 可控 可控

G1特性

引言:

使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个 大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂 如果对象太大,一个Region放不下[超过Region大小的50%],那么就会直接放到H中 设置Region大小:-XX:G1HeapRegionSize=M 所谓Garbage-Frist,其实就是优先回收垃圾最多的Region区域

```
(1)分代收集(仍然保留了分代的概念) 
(2)空间整合(整体上属于“标记-整理”算法,不会导致空间碎片) 
(3)可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)
```

具体来说:

  1. 常规操作尽量避免并发,所以相对于CMS来说CPU负载较低。

  2. 分代收集,逻辑上将堆内存进行重新划分。具体如图

    G1堆逻辑内存结构.png
  3. 将内存划分为大小相等的region。region大小范围在 1~32M之间且为2的幂次方倍。系统默认会将堆内存分为2048个region,这样的话堆内存最小2G

    总结:G1是根据region进行展开的,region是G1的基础。region可以进行角色的转换,如从old转成Eden,region一共有5个角色,分别是:empty space、Eden space、survivor space、old generation和Humongous。其中Humongous用于存储大对象,当对象超过region一半大小的时候认为是大对象会放到Humongous中。

region

  1. region分类:
region分类.png

JVM中对象引用分为两种:Point out(我引用谁)Point in(谁引用我)

问题:G1中region和CMS中card Page 的区别和联系?

  • 区别:

    1. region最小是1M,card Page 是固定大小512K。
  • 联系:

    1. 一个region中包含了多(>=2)个card Page。

    2. card table 是CMS为了解决跨带引用的问题 (PS:*Card Table知识点可以参考 标记整理算法--滑动整理--单次遍历算法 *);

      Rset-- RemeberSet 引用集 通过Point In 来实现 Rset 引用集是为了解决跨Region引用的问题,可以理解为Card Table的升级版

      • Rset 使用到那三种数据结构,作用是什么?

        1. 稀疏表 -- 记录每个region中card page 的索引位置(起始位置)。

          稀疏表本质是哈希表,K-V结构,其中K是每个region的起始地址 V是一个记录了Card Page数据索引号的数组 ;作用:字典,查询每块card page 索引。

        2. 细粒度位图。 -- 记录card Page 中对象引用的变化,一个标识位对应一个card page

          细粒度位图 中记录了region中对象引用的变化,注意:region能分为多少位,他就能分为多少位

        3. 粗粒度位图。 -- 一个标识位对应一个region,当一个region中的card page到达一定数量的时候进行使用。

          总结:Rset 是三种结构搭配来使用的,不是针对单独的一种结构。注意:粗细不同时存在,粗细力度阈值 = 64G,因为G1是并发类垃圾收集器,当多线程环境下位图维护会存在写入乱序的问题,这是G1通过写屏障来进行维护。

          • 写屏障会带来内存伪共享问题,什么是内存伪共享,如何解决?

            写屏障:在并发标记阶段,如果对象引用有变化,这时候会将对象的变化进行记录防止误删重新被引用的对象,类似于Spring AOP的思想。各个垃圾收集器的具体实现可能会略有不同。
              参考:https://www.jianshu.com/p/12544c0ad5c1
            
            内存伪共享:
            
              多核CPU的情况下有多个 L1 和 L2 缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个一致性的协议MESI。
            
            MESI规定了一个cache line存在四种状态:Modified、Exclusive、Shared 和Invalid,这有点像状态机的转换,理清全部的状态较复杂,我们关注简单的:
            
            Modified:该缓存行只被缓存在该CPU的缓存中,并且是被修改过的,即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它CPU读取请主存中相应内存之前)写回主存。当被写回主存之后,该缓存行的状态会变成Exclusive状态。
            Exclusive:该缓存行只被缓存在该CPU的缓存中,它是未被修改过的,与主存中数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成Shared状态。同样地,当CPU修改该缓存行中内容时,该状态可以变成Modified状态。
            Shared:该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致,当有一个CPU修改该缓存行中,其它CPU中该缓存行可以被作废。
            Invalid:该缓存是无效的(可能有其它CPU修改了该缓存行)。
              
              下图中thread0位于core0,而thread1位于core1,二者均想更新彼此独立的两个变量,但是由于两个变量位于同一个cache line中,此时可知的是两个cache line的状态应该都是Shared,而对于cache line的操作core间必须争夺主导权(ownership),如果core0抢到了,thread0因此去更新cache line,会导致core1中的cache line状态变为Invalid,随后thread1去更新时必须通知core0将cache line刷回主存,然后它再从主存中load该cache line进高速缓存之后再进行修改,但令人抓狂的是,该修改又会使得core0的cache line失效,重复上演历史,从而高速缓存并未起到应有的作用,反而影响了性能。
            
            内存伪共享参考:https://zhuanlan.zhihu.com/p/55917869
            
            
            内存伪共享.png

            通过添加参数:-XX:+UserCondCardMark来解决(PS:可以理解为对该内存进行了加锁操作),也是G1针对并发写card Table/region的问题即区内存共享问题,是JVM调优的手段

          Rset三种数据结构.png
  • Rset 缺点:耗内存,占比 5%~10%

  • Rset是同步还是异步?-- 异步

    异步的化通常用队列来处理,Rset声明一个全局的DCQS(Dirty Card Queue Set 用来处理对象引用变化的) 和 G1BarrirSet(用来处理SAB)。

G1执行流程

  • 初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程(STW)
  • 并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
  • 最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程(STW)
  • 筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据 用户所期望的GC停顿时间制定回收计划
G1执行流程.png
  1. 三色标记算法 : 注意这里的是指逻辑状态

    黑色:扫描完的

    灰色:正在扫描的

    白色:未扫描的。

  2. Rset和Card Table 是三色标记算法的落地,CardTable 关注的是引用的增加--当已扫描的A增加了C引用的化,A状态变成正在扫描,会对A再次进行扫描,Rset关注的是引用的变化 -- 当已扫描的A增加了C引用的化,会将C引用放到GC Root中来对变化进行记录。

三色标记算法.png
  1. G1垃圾回收分类:young GC、mixed GC(混合模式:回收整个young区和部分Old区)、Full GC。

其中young GC -- STW

  • young GC执行流程
  1. STW

  2. 锁定整个所有新生代的region

  3. 根扫描

  4. 更新Rset -- 目的:记录我们引用的变化

  5. 存活对象复制--将Eden和Survivor存活对象复制到空闲survivor区

  6. 重构Rset

  7. 释放锁定

  8. 如果存在大对象进行大对象的回收

  9. 动态的扩展内存

  10. 动态调整region数量

  11. 启动并发标记

    初始标记:1~10

  • mixed GC

    mixed GC = young GC + 收益高的Old Region。

    参数:-XXMixedGCTarget = 8 :将Old 区默认分成8份,每次mixed GC 回收整个Young区 + 1/8Old 区。

  • Full GC

    触发条件:没有足够的region

    JDK11之后 并行执行,之前是串行执行

G1相关参数

-XX: +UseG1GC 开启G1垃圾收集器
-XX: G1HeapReginSize 设置每个Region的大小,是2的幂次,1MB-32MB之间 -XX:MaxGCPauseMillis 最大停顿时间
-XX:ParallelGCThread 并行GC工作的线程数
-XX:ConcGCThreads 并发标记的线程数
-XX:InitiatingHeapOcccupancyPercent 默认45%,代表GC堆占用达到多少的时候开始垃圾收集,JDK8之后版本

TLAB (Thread Loacal Allocation Buffer) 线程本地缓冲区

TLAB存在位置?

TLAB 是存在于堆内存的一块线程本地缓冲区,可以理解为堆内存的高速缓冲区。

TLAB解决的问题?

为了解决访问临界区线程需要全局加锁导致效率下降的问题。

TLAB部分源码.png
//TODO 临界区行为:解释。
JNI(Java Native Interface)意为 Java 本地调用,它允许 Java 代码和其他语言写的 Native 代码进行交互。
JNI 如果需要获取 JVM 中的 String 或者数组,有两种方式:

拷贝传递。
共享引用(指针),性能更高。
由于 Native 代码直接使用了 JVM 堆区的指针,如果这时发生 GC,就会导致数据错误。因此,在发生此类 JNI 调用时,禁止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。
参考:https://zhuanlan.zhihu.com/p/291044796

//TODO OOPS:指针压缩技术(Ordinary Object Pointers)。

//TODO Jvmti:是JVM给开发者的一整套的后门。如class加密,热部署,埋点等都会用到JVmti。
TLAB流程图.png

ZGC

ZGC.png

官网: https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0

JDK11新引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了 会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题 只能在64位的linux上使用,目前用得还比较少

优势:

  1. 可以达到10ms以内的停顿时间要求

  2. 支持TB级别的内存,目前最大支持4TB

    64位操作系统中:未压缩的指针是8字节,ZGC使用4字节来记录引用的变更,具体记录的内容:finalizer方法是否被使用的标记、引用集是否被标记过(Dirty Card)、三色标记状态。

    这样64 - 18 - 4 = 42,目前64位的Linux下高18位是不能寻址的。

    2^{42}=4TB

  3. 堆内存变大后停顿时间还是在10ms以内

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容