一、为什么会出现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 04、注意看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。