Java基础之《JVM性能调优(10)—G1的内存模型》

一、为什么会出现G1垃圾回收器

1、自定义STW时间
在G1之前,垃圾回收器停止工作线程STW的时间是不可控的,停多久没人知道
G1就是为了解决这种问题,针对STW可自定义停多久,默认是200ms

2、内存碎片化问题
以CMS为代表的回收器,会产生大量的内存碎片,因为CMS采用了“标记-清除”算法

G1采用“整体+局部”的设计方式避免内存碎片化
整体:基于标记-整理来实现垃圾回收
局部:把整个堆,切割为大小均匀的多个region,region与region之间采用标记-复制算法实现

不会产生内存碎片:G1的内存布局并不是固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region)
G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现,不会像CMS(“标记-清除”算法)那样产生内存碎片

3、年轻代老年代通吃
以往JVM的垃圾回收,都要分别为年轻代、老年代分别设置垃圾回收器,从G1开始就不用了,因为它通吃!

4、适用大堆的应用程序
大应用
服务端多核CPU、JVM内存占用较大的应用(至少大于4G),小于4G的堆内存用CMS

二、内存模型—分区region—卡片card

1、region(分区)
G1堆的大小:依然采用-Xms/-Xmx来指定堆空间大小
堆的切割:堆内存被切割为多个大小固定的区域(Region),而且每个Region内部是连续的虚拟内存
堆默认把内存分为2048个Region,每个Region大小固定,每个Region的取值范围【1m—32m】,是2的幂次方
参数:
-XX:G1heapRegionSize,自定义region的大小

这里面白色代表空,未分配。待定,是eden区、surviver区还是什么 

2、Card(卡片)
G1对内存的分配以分区(Region)为单位,而对象的分配则以卡片(Card)为单位
每个Region都会被切割为均匀的card,每个card的大小为512Byte,card是堆内存最小粒度
所有分区Region的卡片将会记录在全局卡片表(Global Card Table)中

三、内存模型—HumongousObject

1、HumongousObject大对象
一个大小达到甚至超过分区Region 50%以上的对象称为巨型对象(Humongous Object)。巨型对象会独占一个、或多个连续分区

2、大对象有什么特点
Humongous Object有以下特点:
1)Humongous Object直接分配到了老年代,防止了反复拷贝移动
当线程为巨型对象分配空间时,不能简单在TLAB进行分配,TLAB(Thread Local AllocationBuffer,线程本地分配缓冲区),因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。
因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。

2)大对象的回收阶段
大对象的回收并不是在young gc或mixed gc阶段,humongous object在2个阶段回收
1. 全局并发标记(Global Concurrent Marking)阶段的Cleanup
2. FGC阶段

3、例子

package G1;

import java.util.ArrayList;
import java.util.List;

public class YoungGCTest {
    /**
     -verbose:gc
     -Xms10m
     -Xmx10m
     -XX:+UseG1GC
     -XX:+PrintGCDetails
     -XX:+PrintGCDateStamps
     -XX:+PrintGCTimeStamps
     -XX:MaxGCPauseMillis=200m

     一个region 1M(日志最后的region size 1024K)
     一个大小达到甚至超过分区Region 50%以上的对象称为巨型对象(Humongous Object)
     故,低于512k是触发young gc,大于512k是G1 Humongous Allocation
     触发young gc的条件,超过60%堆,即超过6M
     */

    static int size = 1024*512;

    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i=1; i<10; i++) {
            System.out.println("----------"+i+"----------");
            byte[] array = new byte[size];
            list.add(array);
        }
    }
}
"C:\Program Files\Java\jdk1.8.0_291\bin\java.exe" -verbose:gc -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:MaxGCPauseMillis=200m "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=55778:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_291\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_291\jre\lib\rt.jar;D:\workspace_idea\test1\out\production\test1;D:\workspace_idea\test1\junit-4.13.2.jar;D:\workspace_idea\test1\hamcrest-core-2.2.jar;D:\workspace_idea\test1\hamcrest-2.2.jar;D:\workspace_idea\test1\jol-core-0.16.jar" G1.YoungGCTest
----------1----------
----------2----------
----------3----------
----------4----------
----------5----------
2022-04-05T21:18:36.427+0800: 0.105: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0013241 secs]
   [Parallel Time: 0.7 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 104.9, Avg: 104.9, Max: 104.9, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.6, Diff: 0.4, Sum: 2.8]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 1.8]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 8.9, Max: 16, Diff: 15, Sum: 71]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.4]
      [GC Worker Total (ms): Min: 0.6, Avg: 0.6, Max: 0.7, Diff: 0.0, Sum: 5.2]
      [GC Worker End (ms): Min: 105.6, Avg: 105.6, Max: 105.6, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.5 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 2048.0K(6144.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3882.1K(10.0M)->2808.1K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.428+0800: 0.106: [GC concurrent-root-region-scan-start]
2022-04-05T21:18:36.429+0800: 0.106: [GC concurrent-root-region-scan-end, 0.0004662 secs]
2022-04-05T21:18:36.429+0800: 0.106: [GC concurrent-mark-start]
2022-04-05T21:18:36.429+0800: 0.106: [GC concurrent-mark-end, 0.0000309 secs]
2022-04-05T21:18:36.429+0800: 0.107: [GC remark 2022-04-05T21:18:36.429+0800: 0.107: [Finalize Marking, 0.0000987 secs] 2022-04-05T21:18:36.429+0800: 0.107: [GC ref-proc, 0.0000725 secs] 2022-04-05T21:18:36.429+0800: 0.107: [Unloading, 0.0003845 secs], 0.0006539 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.429+0800: 0.107: [GC cleanup 3402K->3402K(10M), 0.0002820 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
----------6----------
----------7----------
----------8----------
----------9----------
2022-04-05T21:18:36.430+0800: 0.108: [GC pause (G1 Humongous Allocation) (young) (to-space exhausted), 0.0034541 secs]
   [Parallel Time: 2.8 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 108.2, Avg: 108.3, Max: 108.4, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 2.8, Diff: 2.8, Sum: 3.6]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.5, Diff: 0.5, Sum: 0.5]
      [Object Copy (ms): Min: 0.0, Avg: 0.9, Max: 1.8, Diff: 1.8, Sum: 7.4]
      [Termination (ms): Min: 0.0, Avg: 1.3, Max: 2.7, Diff: 2.7, Sum: 10.8]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 2.7, Avg: 2.8, Max: 2.8, Diff: 0.1, Sum: 22.3]
      [GC Worker End (ms): Min: 111.0, Avg: 111.0, Max: 111.1, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.6 ms]
      [Evacuation Failure: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 4938.1K(10.0M)->4938.1K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.434+0800: 0.112: [Full GC (Allocation Failure)  4938K->4725K(10M), 0.0018437 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 4938.1K(10.0M)->4725.1K(10.0M)], [Metaspace: 3215K->3215K(1056768K)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.435+0800: 0.114: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0003944 secs]
   [Parallel Time: 0.2 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 113.8, Avg: 113.8, Max: 113.9, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.9]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.1, Max: 1, Diff: 1, Sum: 1]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.1, Sum: 1.1]
      [GC Worker End (ms): Min: 113.9, Avg: 113.9, Max: 113.9, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 5237.2K(10.0M)->5237.2K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.436+0800: 0.114: [GC concurrent-root-region-scan-start]
2022-04-05T21:18:36.436+0800: 0.114: [GC concurrent-root-region-scan-end, 0.0000123 secs]
2022-04-05T21:18:36.436+0800: 0.114: [GC concurrent-mark-start]
2022-04-05T21:18:36.436+0800: 0.114: [GC pause (G1 Evacuation Pause) (young), 0.0005065 secs]
   [Parallel Time: 0.3 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 114.3, Avg: 114.4, Max: 114.5, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.5]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.3, Max: 1, Diff: 1, Sum: 2]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.5]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 1.1]
      [GC Worker End (ms): Min: 114.5, Avg: 114.6, Max: 114.6, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 5237.2K(10.0M)->5237.2K(10.0M)]
 [Times: user=0.03 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.437+0800: 0.115: [Full GC (Allocation Failure)  5237K->628K(10M), 0.0018598 secs]
   [Eden: 0.0B(1024.0K)->0.0B(5120.0K) Survivors: 0.0B->0.0B Heap: 5237.2K(10.0M)->628.4K(10.0M)], [Metaspace: 3216K->3216K(1056768K)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2022-04-05T21:18:36.438+0800: 0.117: [GC concurrent-mark-abort]
Heap
 garbage-first heap   total 10240K, used 628K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
  region size 1024K, 1 young (1024K), 0 survivors (0K)
 Metaspace       used 3307K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

4、注意看cleanup关键字
因为young gc通常会伴随全局并发标记(Global Concurrent Marking),全局并发标记的初始标记会导致STW,而STW成本太高,借助young gc触发,故全局并发标记阶段的cleanup阶段回收了大对象。

四、G1为什么还使用分代技术

1、G1已经把堆内存切割大小均匀的region了,为什么还要使用分代技术?
G1依然沿用了分代技术,因为分代的好处是可以从对象的生命周期集中归类,生命周期短的放在年轻代,周期长的放在老年代,从而避免了整堆扫描,同时也降低了GC的时间(STW时间)。

2、G1分代的2大特点
1)新生代和老年代不再是物理隔离,它们由多个Region组成的集合。因此,G1并不要求对象的存储一定是物理上连续的,只要是逻辑上连续即可。
另外每个分区Region也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。

2)年轻代是不固定的,是动态变化的
eden的大小范围默认是 =【-XX:G1NewSizePercent,-XX:G1MaxNewSizePercent】=【整堆5%,整堆60%】
在【整堆5%,整堆60%】的基础上,G1会计算下现在eden区回收大概要多久时间,如果回收时间远远小于参数-XX:MaxGCPauseMillis设定的值,那么增加年轻代Region,继续给新对象存放,不会马上YoungGC。
G1计算回收时间接近参数-XX:MaxGCPauseMillis设定的值为止。
另外eden和survivor的比例也是8:1:1。
当然,G1依然可以设置固定的年轻代大小(参数-XX:NewRatio、-Xmn),但同时暂停目标将失去意义。

五、分代回收的特点

1、YoungGC
YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数-XX:MaxGCPauseMillis设定的值,那么增加年轻代的region,继续给新的对象存放,不会马上做YoungGC。
直到下一次Eden区放满,G1计算回收时间接近参数-XX:MaxGCPauseMillis设定的值,那么就会触发YoungGC。

2、MixedGC(混合回收)
不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发(默认45%),回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象,就会触发一次FullGC。

3、FullGC
停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。

六、本地分配缓冲区 Local allocation buffer (Lab)
一个对象创建后,首先要给该对象分配一块内存空间,谁来分配内存空间呢?线程!
线程与线程之间就会涉及线程安全问题,于是就必须给每条线程分配单独存储空间LAB。
即,每个线程都有自己独立的一个缓存区,用来分配的,这样就保证了线程安全。

线程安全问题:
第一种解决方案是先独立分配好
第二种解决方案是加锁

有3种LAB:
1、应用线程本地缓冲区TLAB
每次分配对象时,应用线程创建对象时为了保证线程安全,它有自己独立的一个缓存区,用来分配对象,这个缓冲区叫TLAB(线程本地分配缓冲区)。
TLAB创建的对象大部分,会落入eden区域(巨型对象或分配失败除外),TLAB的分区属于eden空间。

2、GC线程本地缓冲区GCLAB
每次垃圾回收时,每条GC线程要把生命周期长的对象复制到suvivor或old空间,每个GC线程同样可以独占一个本地缓冲区(GCLAB)用来转移对象,每次回收会将对象复制到suvivor空间或old空间。

3、晋升本地缓冲区PLAB
每次对象的晋升时,从eden/survivor空间晋升到survivor/old空间的对象,同样有GC独占的本地缓冲区进行操作,该部分称为晋升本地缓冲区(PLAB)。

七、JVM如何避免Young GC时扫描全堆(CardTable技术)
1、背景
如果要触发ygc,如何识别D、E是否为垃圾对象?
A很好处理,通过gcroot,扫描年轻代,就直接判断A是存活的。
但是D、E就麻烦了,因为不知道有没有人引用D、E,故必须扫描整个老年代,看谁引用了D、E,最终通过扫描老年代的所有对象(BXYZW),得知D是被老年代引用的,判断D为存活,E无人引用。
以上D这种现象,称之为跨代引用或跨代扫描(大概1%)。为了判断D、E是否存活,成本极其高,要扫描整个老年代。

2、如何解决
那如何解决这个问题呢?不可能每次ygc都去扫描整个堆吧?
jvm设计了一种叫做记忆集的方法来实现。
它的实现原理:谁发生跨代引用就把它记录下来,例如D被老年代C引用,就把C记录下来。
技术实现如下:
1)先把内存划分为大小相同的Card(卡片),每个card的大小为512Byte

2)整个老年代的card连起来就成了一张card table,记录可能存在的老年代中有新生代的引用的对象地址,来避免全堆扫描。

公式:CARD_TABLE[this address >> 9] = 0;
index = this address >> 9:把对象的地址的值右移9位相当于除以512就是卡表索引index,每512字节为一组,对应卡表同一个元素,一组就是一个卡页。
每个card可以存储多个对象,如果只要有其中一个对象引用了年轻代,此card的value就变脏dirty,设置为1。
例如:对象C引用了D,C的指针地址800,cardtable_index=800/512=1,故把cardtable_1=1。
故,cardtable其实就是映射着内存中的对象,便可以不用扫描整个老年代,只要在cardtable寻找脏卡就行。
在进行YoungGC的时候,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把他们加入GCRoots中并扫描。
例如,把CARD_TABLE[1]里面的所有对象(包括C)进行gc roots就能找出D,card table卡表能用于减少老年代的全堆空间扫描,这能很大的提升GC效率。

八、什么时候对cardtable赋值

1、写屏障
Card Page中对象的引用发生改变时,写屏障逻辑将会把Card Page在Card Table数组中对应的值标记为dirty(标记为1)。

2、什么是写屏障
先看如下伪代码:
给某个对象的成员变量赋值时:

/**
* @param field 某对象的成员变量,如:a、b、c
* @param new_value 新值,如null
*/
void oop_field_store(oop* field, oop new_value) {
*field = new_value; //赋值操作
}

所谓写屏障(write_barrier),其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):

void oop_field_store(oop* field, oop new_value) {
pre_write_barrier(field); //写屏障-写前操作
*field = new_value;
post_write_barrier(field, value); //写屏障-写后操作
}

在赋值前的部分的write barrier叫做pre-write barrier,在赋值后的则叫做post-write barrier。

九、G1 JVM如何避免Young GC时扫描老年代(RSet技术)

1、背景
以往的跨代引用,只有年轻代、老年代2块数据的相互引用,它可以采用cardtable来解决。

但是现在的G1的设计是把堆,均匀按region切割为n个。 

2、G1不能用cardtable来解决了
D是年轻代region,被C引用,C被X引用,他们分别在不同的region,这种情况下再使用cardtable就没那么好处理了。
因为cardtable在设计上是针对card来记录的,现在除了要记录card还要记录region,cardtable就明显做不了了。
在这样一种背景下,G1设计了RSet。

3、RSet
G1采用RSet来识别对象是否存活。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。

什么是RSet?
在串行和并行收集器中,GC通过整堆扫描,来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet)。
RSet全程是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。
RSet其实是一个Hash Table,Key是其他的Region的起始地址,Value是一个集合,里面的元素是Card Table数组中的index,即Card对应的index,映射到对象的Card地址。
RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card覆盖覆盖一定范围的Heap(一般为512Bytes)。

4、例子

1)Region1对象A引用了Region2对象C,对象A在card1
2)Region3对象B引用了Region2对象D,对象B在card2
3)Region1和Region3中有对象引用了Region2的对象,则Region2的RSet
RSet=Hash Table,既然是Hash Table,它就有key和value
key1=Region1起始地址,Value={card1在Card Table数组中的index}
key2=Region3起始地址,Value={card2在Card Table数组中的index}
故,判断C、D是否存活,到RSet里面查看是否有引用关系就知道了。

如果没有RSet,如何判断C、D是否存活呢?先扫描Region2所有对象,然后再到Region1和Region3判断是否被引用了,最终判断C、D被引用存活。
故,如果没有RSet,要扫描所有的region,即对整个堆进行扫描,非常耗时。

十、哪些引用的关系需要记录在RSet中

1、分为5种情况
1)分区内部的引用
不需要。无论是新生代还是老年代的分区内部的引用,都不需要记录引用关系。因为是针对一个分区进行的垃圾回收,要么这个分区被回收,要么不被回收。
2)新生代引用新生代
不需要。G1的三种回收算法(YGC/MIXED GC/FULL GC)都会全量处理新生代分区,所以新生代都会被遍历到。因此无需记录这种引用关系。
3)新生代引用老年代
无需记录。G1的YGC回收新生代,无需这个引用关系。混合GC时,G1会采用新生代分区作为根,那么在遍历新生代分区时就能找到老年代分区了,无需这个引用关系。对于FULL GC来说,所有分区都会被处理,也无需这个引用关系。
4)老年代引用新生代
需要记录。YGC在回收新生代时,如果新生代的对象被老年代引用,那么需要标记为存活对象。即此时的根对象有两种,一个是栈空间/全局变量的引用,一个是老年代到新生代的引用。
5)老年代引用老年代
需要记录。混合GC时,只会回收部分老年代,被回收的老年代需要正确的标记哪些对象存活。

2、Per Region Table(PRT)
RSet在内部使用Per Region Table(PRT)记录分区的引用情况。
由于RSet的记录要占用分区的空间,如果一个分区的对象引用非常多,那么RSet占用的空间会上升,从而降低分区的可用空间。
为了提高效率,用了动态化的数据结构存储。

在PRT中有3种数据结构存储:
稀少:直接记录引用对象的card索引
细粒度:记录引用对象的region索引
粗粒度:只记录引用情况,每个region对应一个比特位
由上可知,粗粒度的PRT只是记录了引用数量,需要通过整堆扫描才能找出所有引用,因此扫描速度也是最慢的。

十一、谁来负责维护RSet

1、背景
一个应用程序,有无数个像这种代码:objX.fieldY = objZ,一直不停的运行着。
大量的引用变更,要维护RSet的话,必定会拖垮应用线程。现在面临的问题是如何高效的写RSet?
采用写屏障来处理,如下图:

G1采用写屏障+并发优化线程Refine来维护RSet。

2、RSet线程
RSet有2种线程和一个全局队列DirtyCardQueueSet(DCQS),来共同维护。

应用线程mutator
1)应用线程通过写屏障,找到该字段(fieldY)所在的位置(Card),并设置为Dirty_Card。
2)每条应用线程都有一个私有队列,把card放进缓存队列Dirty Card Queue(DCQ),每个队列有256的长度,当队列满时,就把DCQ的card移到DCQS,清空DCQ。

Refine线程
1)Refine线程异步读取队列里面的Card的地址,计算出Card所在的Region。
2)最终把card加入region的RSet中。

实际上除了Refine线程更新RSet之外,GC线程(young gc的Update RS阶段)或者Mutator也可能会更新RSet。(G1的young gc)

3、Refinement threads线程数量
Refinement threads线程数量可以通过-XX:G1ConcRefinementThreads(默认等于-XX:ParallelGCThreads)参数设置。
如果记录增长很快或者来不及处理,那么通过阈值-XX:G1ConcRefinementGreenZone/-XX:G1ConcRefinementYellowZone/-XX:G1ConcRefinementRedZone,G1会用分层的方式调度,使更多的线程处理全局队列。
如果并发优化线路也不能跟上缓冲区数量,则Mutator线程(Java应用线程)会挂起应用(STW)并被加进来帮助处理,直到全部处理完。因此,必须避免此类场景出现。

4、关于Refine线程
Refine线程是G1新引入的并发线程池,线程默认数目为-XX:G1ConcRefinementThreads(默认0)+1
它的作用有2点:
1)用于处理新生代分区的抽样,并且在满足响应时间的这个指标下,更新YHR(Young Heap Region)的数目。
2)维护RSet。

十二、什么是CSet,它有什么作用?

1、CSet
CSet(Collection Set):没有存活对象的region在evacuation(回收)阶段统一收集存放到一个集合中,这个集合名叫CSet(收集集合)。
CSet代表每次GC要回收的一系列目标分区。在任意一次GC后,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。
年轻代收集CSet只容纳年轻代分区,混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。

2、上图中,在YoungGC的时候,young CSet存的是Young Region。在MixedGC的时候,mixed CSet存了所有的Young region和部分的Old region。
 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值