画布 1
Garbage Collector
  CMS
    新生代
      Parnew
        标记阶段
          即从GC Roots集合开始，标记活跃对象
        转移阶段
          即把活跃对象复制到新的内存地址上
        重定位阶段
          因为转移导致对象的地址发生了变化，在重定位阶段，所有指向对象旧地址的指针都要调整到对象新的地址上。
    老年代
      步骤
        初始标记
        并发标记
        再标记
        并发收集
      card table
  G1
    这里面所有的操作之间都可以间隔 jvm 进入安全点的其他操作： Total time for which application threads were stopped
    新生代
      young gc，是 stw gc
        标记阶段
          即从GC Roots集合开始，标记活跃对象
        转移阶段
          即把活跃对象复制到新的内存地址上。
            G1 的 survivor 会和 eden 一起被复制到空白 region，成为新的 survivor。
        重定位阶段
          因为转移导致对象的地址发生了变化，在重定位阶段，所有指向对象旧地址的指针都要调整到对象新的地址上。
    老年代
      触发 mixed gc 的参数为：MaxGCPauseMillis 和 InitiatingHeapOccupancyPercent。mixed gc 全量回收年轻代，而部分回收老年代。g1 为了达到 soft real time 的 pause target，会调整整个 young 区的大小，和 CSet 里 old 区的大小。
      mixded gc，是 stw gc
        young gc
        tenure gc
          全局并发标记（global concurrent marking）
            初始标记
              根分区扫描（在有的文献里，这不是初始标记的一部分，而是个单独的 phase）
                这个过程不需要暂停应用，在初始标记或新生代收集中被拷贝到survivor分区的对象，都需要被看做是根，这个阶段G1开始扫描survivor分区，所有被survivor分区所引用的对象都会被扫描到并将被标记
            并发标记
              三色算法 Snapshot At The Beginning
                黑色
                  根对象，或者该对象与它的子对象（但不包括孙对象）都被扫描
                灰色
                  对象本身被扫描,但还没扫描完该对象中的子对象
                白色
                  未被扫描对象，扫描完成所有对象之后，最终为白色的为不可达对象，即垃圾对象
              pre-write barrier
                当（feild）引用关系发生变化的时候，通过pre-write barrier函数会把这种这种变化记录并保存在一个队列里，在JVM源码中这个队列叫satb_mark_queue。在remark阶段会扫描这个队列，通过这种方式，旧的引用所指向的对象就会被标记上，其子孙也会被递归标记上。
CMS 也要解决这个问题，但 g1 这个问题解决得更好，stw 的风险更低。因为 CMS 还需要扫描所有的 GC Roots
            再标记
            清除垃圾（Cleanup）
              这个阶段如果发现完全没有活对象的region就会将其整体回收到可分配region列表中。 清除空Region。甲骨文的官方文档描述它为 partly concurrent partly STW，gc log 也显示有 application stopped time。
          复制（Evacuation）
            Evacuation阶段是全暂停的（G1未能解决转移过程中准确定位对象地址的问题，这点不如 CMS）。它负责把一部分region里的活对象拷贝到空region里去（并行拷贝），然后回收原本的region的空间。Evacuation阶段可以自由选择任意多个region来独立收集构成收集集合（collection set，简称CSet），CSet集合中Region的选定依赖于上文中提到的停顿预测模型，该阶段并不evacuate所有有活对象的region，只选择收益高的少量region来evacuate，这种暂停的开销就可以（在一定范围内）可控。
            这种可控性就是 mixed gc 的优异之处，也是 g1 的 garbage first 的含义
      full gc
        当Mixed GC的速度赶不上应用程序申请内存的速度的时候，Mixed G1就会降级到Full GC，使用的是Serial GC。Full GC会导致长时间的STW，应该要尽量避免。所以 full gc 严格来讲不是 g1 进行的，serial 长期存在的用处，也是作为各种 gc 的回退措施，它不支持并发/并行收集，反而不会有浮动垃圾，导致这些模式失败。
    内存分区，默认为 512k，通常设为 4m
      Eden
      Survivor
      Old
      Humongous
        大于 region 的一半时，就直接分配在这个区。如果找不到合适的分区，JVM 可能采用 full gc 进行垃圾回收，以腾出（连续的）空间。
防止大对象的方式是调大 XX:G1HeapRegionSize
    JVM 参数
      JVM ARGS
        -server
          服务器模式
        -Dfile.encoding=UTF-8
          文件编码
        -Dsun.jnu.encoding=UTF-8
          文件编码
        -Djava.io.tmpdir=/tmp
          IO 临时文件夹
        -Djava.net.preferIPv6Addresses=false
          不使用 IPv6
        -Duser.timezone=GMT+08
          用户时区
      JVM GC ARGs
        ParallelGCThreads
          8 个处理器以下，线程数为 8。这里的 parallel 是并行垃圾收集线程的意思。在 4C8G 的服务器节点上，通常就是 4
          8 个处理器以上，5/8
        ConcGCThreads
          ParallelGCThreads 的 1/4。这里的 con 是并发标记的意思。
        -XX:+UseG1GC
          使用 G1 垃圾收集器
        -XX:G1HeapRegionSize=4M
          区域大小，能够把堆区分为 2048 个区域为好
        -XX:InitiatingHeapOccupancyPercent=40
          堆占用阈值
        -XX:MaxGCPauseMillis=100
          最大 gc 停顿，越高停顿越高，越低吞吐越低
        -XX:+TieredCompilation
          打开分层编译
        -XX:CICompilerCount=4
          最大并行编译数
        -XX:-UseBiasedLocking
          关闭偏向锁
        -XX:+PrintGCDetails
          打印 gc 细节
        -XX:+PrintHeapAtGC
          gc 时打印堆状况
        -XX:+PrintTenuringDistribution
          gc 时打印老年代分布
        -XX:+PrintGCTimeStamps
          gc 时打印时间戳
        -XX:+PrintGCDateStamps
          gc 时打印日期戳
        -XX:+PrintStringTableStatistics
          gc 时打印字符串表统计
        -XX:+PrintAdaptiveSizePolicy
          gc 时打印响应策略
        -XX:+PrintGCApplicationStoppedTime
          gc 时打印应用程序停顿时间
        -XX:+PrintFlagsFinal
          打印最终的标志位
        -XX:-UseGCLogFileRotation
          gc 日志滚动
        -XX:NumberOfGCLogFiles=10
          gc 日志文件数量
        -XX:GCLogFileSize=10M
          gc 文件大小
        XX:MaxDirectMemorySize
          直接内存大小，到达这个值以后会触发 full gc
      JVM_HEAP
        -XXmn/NewRatio
        -Xss512k
        -Xmx$(实际内存/2)m
        -Xms$(实际内存/2)m
        -XX:MetaspaceSize=$(实际内存/16)m
          Metaspace扩容时触发FullGC（局部被称作 MetaGC）的初始化阈值，也是最小的阈值
        -XX:MaxMetaspaceSize=$(实际内存/16)m
          默认是无穷大，是真正的元空间大小，而不是 gc 大小
        -XX:+AlwaysPreTouch
        -XX:+HeapDumpOnOutOfMemoryError
        MetaspaceSize
        MaxMetaspaceSize
    CSet
      一组可被回收的分区的集合，类似 survivor和 eden，是内存回收的 FROM 的意思。它记录了GC要收集的Region集合，集合里的Region可以是任意年代的。一般垃圾收集器会选好全部的 young region，再追加一些 old region。
        interruptible mixed gc 可以优化这个区域的收集效率
    RSet
      RSet记录了其他Region中的对象引用本Region中对象的关系，属于points-into结构（谁引用了我的对象）。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象，只需要扫描RSet即可。
    gc log
      ygc 和 mixedgc 的分界点
        2021-10-04T11:10:59.083+0800: 934473.463: [GC pause (G1 Evacuation Pause) (young)
          start choosing CSet
          add young regions to CSet, eden
          finish choosing CSet, eden:
          一般会有 3 个 [G1Ergonomics (CSet Construction) 里面紧跟一个 [G1Ergonomics (Concurrent Cycles)
request concurrent cycle initiation, reason:
            occupancy higher than threshold
老年代的占用超过阈值是一个最常出现的问题
              occupancy: 1719664640 bytes, allocation request: 0 bytes, threshold: 1717986880 bytes (40.00 %), source: end of GC]
通常垃圾收集器的阈值也是自己定的，有时候就会定为 40%
      934487.614: [G1Ergonomics (Concurrent Cycles) initiate concurrent cycle, reason: concurrent cycle initiation requested]
      2021-10-04T11:11:13.234+0800: 934487.614: [GC pause (G1 Evacuation Pause) (young) (initial-mark)
它还有一种变种：
[GC pause (G1 Humongous Allocation) (young) (initial-mark)
        注意，初始标记还只是标记阶段，所以还是只有 3 个构造 cset 的日志，而且cset 里 old 通常为 0。
934487.615: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 493 regions, survivors: 9 regions, old: 0 regions, predicted pause time: 24.77 ms, target pause time: 100.00 ms]
        这一次的标记时间也通常比较久，在 4g 的堆上可以做到 80ms 的 stw 停顿。这一次不只是标记，同样会回收大量的区域。
      2021-10-04T11:11:13.336+0800: 934487.716: [GC concurrent-root-region-scan-start]
      2021-10-04T11:11:13.376+0800: 934487.756: [GC concurrent-root-region-scan-end, 0.0393295 secs]
      2021-10-04T11:11:13.376+0800: 934487.756: [GC concurrent-mark-start]
      2021-10-04T11:11:13.736+0800: 934488.116: [GC concurrent-mark-end, 0.3598514 secs]
      2021-10-04T11:11:13.739+0800: 934488.119: [GC remark 934488.119: [Finalize Marking, 0.0027112 secs] 934488.122: [GC ref-proc, 0.4271225 secs] 934488.549: [Unloading, 0.0715392 secs], 0.5036053 secs]
      [Times: user=0.43 sys=0.39, real=0.50 secs]
      2021-10-04T11:11:14.246+0800: 934488.627: [GC cleanup 1753M->1737M(4096M), 0.0076207 secs]
      [Times: user=0.03 sys=0.01, real=0.01 secs]
      2021-10-04T11:11:14.255+0800: 934488.635: [GC concurrent-cleanup-start]
      2021-10-04T11:11:14.255+0800: 934488.635: [GC concurrent-cleanup-end, 0.0000520 secs]
      2021-10-04T11:11:27.200+0800: 934501.580: [GC pause (G1 Evacuation Pause) (young)
        934501.607: [G1Ergonomics (Mixed GCs) start mixed GCs, reason: candidate old regions available, candidate old regions: 404 regions, reclaimable: 1429100632 bytes (33.27 %), threshold: 5.00 %] 这时候的 youngc 就包含 reclaimable 的 old region 了
        开始 mixed gc 的日志出现在 younggc 的日志里。
三行 cset 日志以后会出现第四行，因为前面标记完成了，这一次 ygc 才有这一行日志。
这个 gc 也只持续了 30ms
      2021-10-04T11:11:28.548+0800: 934502.928: [GC pause (G1 Evacuation Pause) (mixed)
        这个回收大概也只是 30ms
        三行 mixed gc 才有的 cset 日志
          934504.269: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: old CSet region num reached max, old: 103 regions, max: 103 regions]
          934504.269: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 44 regions, survivors: 7 regions, old: 103 regions, predicted pause time: 67.70 ms, target pause time: 100.00 ms]
          934504.307: [G1Ergonomics (Mixed GCs) continue mixed GCs, reason: candidate old regions available, candidate old regions: 198 regions, reclaimable: 635413240 bytes (14.79 %), threshold: 5.00 %]
        [Eden: 176.0M(176.0M)->0.0B(184.0M) Survivors: 28.0M->20.0M Heap: 1419.8M(4096.0M)->846.3M(4096.0M)]
        eden 的回收大小，不足以说明 heap 回收的大小，heap 大小的差值就是被回收的 old region 的大小
      2021-10-04T11:11:29.886+0800: 934504.266: [GC pause (G1 Evacuation Pause) (mixed)
      2021-10-04T11:11:31.017+0800: 934505.397: [GC pause (G1 Evacuation Pause) (mixed)
      2021-10-04T11:11:32.473+0800: 934506.853: [GC pause (G1 Evacuation Pause) (mixed)
      接下来一分钟里会有 26 个普通的 ygc，age逐渐增加。young region 的数量逐渐从 5 个达到峰值的 500 个。这多轮的 ygc 回收就相当于一次冷启动。
这样会导致这一分钟的 gctime 总时间达到 500ms，也会导致响应的 tp999 从 80ms 跃迁至 110ms
      gc 结果
        garbage-first heap total 4194304K, used 452064K [0x00000006c0000000, 0x00000006c0402000, 0x00000007c0000000)
        region size 4096K, 7 young (28672K), 7 survivors (28672K)
        Metaspace used 176764K, capacity 183398K, committed 185856K, reserved 1212416K
        class space used 20392K, capacity 21566K, committed 22016K, reserved 1048576K
      并发标记的其他阶段，消耗了近 500ms，stw 也有这么多
      cleanup 不到 10ms
      每个 mixed gc 会持续 20-40ms，最后一个 ygc 一般持有 15 ages，第一个 mixed gc 会有 15 个 ages，接下来的mixed gc 里 ages 就会减少了
  ZGC
    STW 的时间只和 gc root 的大小有关，其他时候都在并发处理堆
    gc 阶段
      初始标记
        依赖于 gc root，时间短
      并发标记/对象重定位
      再标记
      并发转移准备
      初始转移
        依赖于 gc root，时间短
      并发转移
    关键技术
      着色指针
      读屏障
    JVM 参数
      -Xms -Xmx：堆的最大内存和最小内存，这里都设置为10G，程序的堆内存将保持10G不变。
      -XX:ReservedCodeCacheSize -XX:InitialCodeCacheSize：设置CodeCache的大小， JIT编译的代码都放在CodeCache中，一般服务64m或128m就已经足够。我们的服务因为有一定特殊性，所以设置的较大，后面会详细介绍。
      -XX:+UseZGC：启用ZGC的配置。
      -XX:ConcGCThreads：并发回收垃圾的线程。默认是总核数的12.5%，8核CPU默认是1。调大后GC变快，但会占用程序运行时的CPU资源，吞吐会受到影响。
      -XX:ParallelGCThreads：STW阶段使用线程数，默认是总核数的60%。
      -XX:ZCollectionInterval：ZGC发生的最小时间间隔，单位秒。
      -XX:ZAllocationSpikeTolerance：ZGC触发自适应算法的修正系数，默认2，数值越大，越早的触发ZGC。
      -XX:-ZProactive：是否启用主动回收，默认开启，这里的配置表示关闭
      -Xlog：设置GC日志中的内容、格式、位置以及每个日志的大小。这个参数是 Java 9 特有的，能够指定日志的格式和布局。
      -XX:+UnlockDiagnosticVMOptions
      -XX:+UnlockExperimentalVMOptions
      打开这两个选项，PrintFlagsFinal 才能打出最终的信息来
  只有进入 safepoint 以后，gc 才可以开始。Safepoint的选定既不能太少以至于让GC等待时间太长，也不能过于频繁以致于过分增大运行时的负荷。
安全点的初始目的并不是让其他线程停下，而是找到一个稳定的执行状态。在这个执行状态下，Java虚拟机的堆栈不会发生变化。这么一来，垃圾回收器便能够“安全”地执行可达性分析。只要不离开这个安全点，Java虚拟机便能够在垃圾回收的同时，继续运行这段本地代码。