G1 gc 分析

G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:

  1. 内存碎片:G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片
  2. 停顿时间:G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间
  3. 并发:与CMS收集器一样,能与应用程序线程并发执行

重要概念

  • jvm gc 算法
    stw:挂起户线程,进行gc root对象可达性分析,垃圾标记

CMS和G1对比

CMS最重要目标:最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。
相比与 CMS 收集器,G1 收集器两个最突出的改进是:
1 基于标记-整理算法,不产生内存碎片。
2 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。

问题1:为什么会stw?

假如不会发生stw,当gc线程标记好一个对象的同时(该对象没有重载finalize方法),用户线程又将该对象重新引用加入到GC Roots中,那么就会发生gc掉不该gc的对象
从GC Roots节点找引用链,整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话分析结果准确性就无法得到保证

问题2:用户线程在什么地方挂起?如何恢复?

safepoint可以理解成是在用户代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,比如需要GC,可以在这个位置暂停,暂停是通过中断来完成的
这些特定的指令(safepoint)位置主要在:
1.循环的末尾
2.方法临返回前 / 调用方法的call指令后
3.可能抛异常的位置
假如线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起,JVM也显然不太可能等待线程重新被分配CPU时间。对于这种情况,就需要安全区域(Safe Region)来解决。

  • Safe Region 安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint
挂起
void SafepointSynchronize::begin() {
  Thread* myThread = Thread::current();
  assert(myThread->is_VM_thread(), "Only VM thread may execute a safepoint");

  if (PrintSafepointStatistics || PrintSafepointStatisticsTimeout > 0) {
    _safepoint_begin_time = os::javaTimeNanos();
    _ts_of_current_safepoint = tty->time_stamp().seconds();
  }
唤醒
SafepointSynchronize::end()
// Start suspended threads
    for(JavaThread *current = Threads::first(); current; current = current->next()) {
      // A problem occurring on Solaris is when attempting to restart threads
      // the first #cpus - 1 go well, but then the VMThread is preempted when we get
      // to the next one (since it has been running the longest).  We then have
      // to wait for a cpu to become available before we can continue restarting
      // threads.
      // FIXME: This causes the performance of the VM to degrade when active and with
      // large numbers of threads.  Apparently this is due to the synchronous nature
      // of suspending threads.
      //
      // TODO-FIXME: the comments above are vestigial and no longer apply.
      // Furthermore, using solaris' schedctl in this particular context confers no benefit
      if (VMThreadHintNoPreempt) {
        os::hint_no_preempt();
      }
      ThreadSafepointState* cur_state = current->safepoint_state();
      assert(cur_state->type() != ThreadSafepointState::_running, "Thread not suspended at safepoint");
      cur_state->restart();
      assert(cur_state->is_running(), "safepoint state has not been reset");
    }

JVM参数 -XX:+PrintGCApplicationStoppedTime, 可以打出系统停止的时间

问题3:为什么用户线程暂停时间长达5s?

log Total time for which application threads were stopped: 5.1298200 seconds
大概率的原因是当发生GC时,有线程迟迟进入不到safepoint进行阻塞,导致其他已经停止的线程也一直等待,VM Thread也在等待所有的Java线程挂起才能开始GC,这里需要分析业务代码中是否存在有界的大循环逻辑,可能在JIT优化时,这些循环操作没有插入safepoint检查。

  • java提供了一个finalize方法,可以帮助我们进行资源释放,类似于C++中的析构函数。但是目前普遍的认识是不要使用
    因为只要finalize方法没有执行,那么这些对象就会一直存在堆区,及时该对象没有引用,知道finalize方法执行完成之后,才会被GC掉

问题4:反复提到的GC Roots是什么?

通过GCRoots所引用的对象作为起始点,从这个节点向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GCRoots没有任何ReferenceChain相连时,(图论:这个对象不可到达),则证明这个对象不可用。

image.png

可以作为GC Root 对象如下:

  1. 本地栈变量
  2. 类的静态属性
  3. 常量
  4. 本地方法栈变量
    都是gcroot对象
  • 在HotSpot的实现中通过OopMap(Ordinary Object Pointer)来维护对象引用,通过OopMap就可以找到堆中的对象,这些对象就是GC Roots

问题5:YGC是否需要扫描整个老年代?

RSet究竟是怎么辅助GC的呢?在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。 而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。
所以G1中YGC不需要扫描整个老年代,只需要扫描Rset就可以知道老年代引用了哪些新生代中的对象。

复制算法-新生代

  1. 会stw,不会产生内存碎片,对象复制,更改引用地址
  2. 每次gc之后对象age+1,age>=6 提升为老年代
  3. 内存空间浪费,所以优化为e:s:s = 8:1:1

标记清除算法-老年代

  1. 标记阶段stw
  2. 清除阶段,不进行对象复制,效率高,在回收后会产生大量不连续的内存空间,即内存碎片

标记整理算法-老年代

  1. 标记阶段stw
  2. 清除阶段,进行对象复制,整理内存空间所有活着的对象向内存空间一端移动,在回收后不会产生内存碎片

g1 gc内存分布

g1 gc 将STW停顿的时间和分布变成可预期以及可配置 region-2048个


image.png
  • 这样的划分使得 GC不必每次都去收集整个堆空间, 而是以增量的方式来处理: 每次只处理一部分小堆区,称为此次的回收集(collection set). 每次暂停都会收集所有年轻代的小堆区, 但可能只包含一部分老年代小堆区
  • G1的另一项创新, 是在并发阶段估算每个小堆区存活对象的总数。用来构建回收集(collection set)的原则是: 垃圾最多的小堆区会被优先收集。这也是G1名称的由来: garbage-first
  • G1 gc 减少内存碎片产生:做法是用regions代替细粒度的空闲列表进行分配,减少内存碎片的产生。第二点:G1的STW更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间
  1. 红色的是eden regions和survivor regions组成young generation,这些region通常是以非连续的区间在内存中排列
  2. 蓝色的是regions和浅蓝色的humongous("H") for 组成old generation,humongous可能跨多个连续区域(放大对象,一个region放不下的那种),起始区域(region)humongous start之后是humongous continues,如果没有足够连续可用空间,则full gc

young gc

复制的过程称为转移(Evacuation),原理就是复制算法

1482.980: [GC pause (G1 Evacuation Pause) (young), 0.0047050 secs]
 – G1转移暂停,只清理年轻代空间。暂停在JVM启动之后 1482.980 ms 开始, 持续的系统时间为 0.0047050 秒 。
   [Parallel Time: 2.0 ms, GC Workers: 8]
– 表明后面的活动由8个 Worker 线程并行执行, 消耗时间为2.0 毫秒(real time)。
      [GC Worker Start (ms): Min: 1482980.7, Avg: 1482981.0, Max: 1482981.2, Diff: 0.6]
- 第一个垃圾收集线程开始工作时JVM启动后经过的时间(min);最后一个垃圾收集线程开始工作时JVM启动后经过的时间(max);diff表示min和max之间的差值。理想情况下,你希望他们几乎是同时开始,即diff趋近于0
      [Ext Root Scanning (ms): Min: 0.3, Avg: 0.5, Max: 0.8, Diff: 0.6, Sum: 4.2]
- 扫描root集合(线程栈、JNI、全局变量、系统表等等)花费的时间,扫描root集合是垃圾收集的起点,尝试找到是否有root集合中的节点指向当前的收集集合(CSet)
      [Update RS (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.7]
每个region都有自己的RSet,用来记录其他分区指向当前分区的指针,如果RSet有更新,G1中会有一个post-write barrier管理跨分区的引用——新的被引用的card会被标记为dirty,并放入一个日志缓冲区,如果这个日志缓冲区满了会被加入到一个全局的缓冲区,在JVM运行的过程中还有线程在并发处理这个全局日志缓冲区的dirty card。Update RS表示允许垃圾收集线程处理本次垃圾收集开始前没有处理好的日志缓冲区,这可以确保当前分区的RSet是最新的
         [Processed Buffers: Min: 0, Avg: 0.9, Max: 1, Diff: 1, Sum: 7]
这表示在Update RS这个过程中处理多少个日志缓冲区
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
扫描每个新生代region的RSet,找出有多少指向当前分区的引用来自CSet
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
扫描代码中的root节点(局部变量)花费的时间
      [Object Copy (ms): Min: 0.4, Avg: 0.5, Max: 0.6, Diff: 0.2, Sum: 3.7]
在疏散暂停期间,所有在CSet中的分区必须被转移疏散,Object Copy就负责将当前分区中存活的对象拷贝到新的分区。
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
当一个垃圾收集线程完成任务时,它就会进入一个临界区,并尝试帮助其他垃圾线程完成任务(steal outstanding tasks),min表示该垃圾收集线程什么时候尝试terminatie,max表示该垃圾收集回收线程什么时候真正terminated。
         [Termination Attempts: Min: 1, Avg: 3.8, Max: 6, Diff: 5, Sum: 30]
如果一个垃圾收集线程成功盗取了其他线程的任务,那么它会再次盗取更多的任务或再次尝试terminate,每次重新terminate的时候,这个数值就会增加。
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.3]
垃圾收集线程在完成其他任务的时间
      [GC Worker Total (ms): Min: 0.9, Avg: 1.1, Max: 1.5, Diff: 0.6, Sum: 9.1]
展示每个垃圾收集线程的最小、最大、平均、差值和总共时间
      [GC Worker End (ms): Min: 1482982.1, Avg: 1482982.1, Max: 1482982.1, Diff: 0.0]
min表示最早结束的垃圾收集线程结束时该JVM启动后的时间;max表示最晚结束的垃圾收集线程结束时该JVM启动后的时间。理想情况下,你希望它们快速结束,并且最好是同一时间结束。
   [Code Root Fixup: 0.0 ms]
释放用于管理并行垃圾收集活动的数据结构,应该接近于0,该步骤是线性执行的;
   [Code Root Purge: 0.0 ms]
清理更多的数据结构,应该很快,耗时接近于0,也是线性执行。
   [Clear CT: 0.9 ms]
清理card table
   [Other: 1.8 ms]
      [Choose CSet: 0.0 ms]
选择要进行回收的分区放入CSet(G1选择的标准是垃圾最多的分区优先,也就是存活对象率最低的分区优先)
      [Ref Proc: 0.8 ms]
处理Java中的各种引用——soft、weak、final、phantom、JNI等等。
      [Ref Enq: 0.0 ms]
遍历所有的引用,将不能回收的放入pending列表
      [Redirty Cards: 0.7 ms]
在回收过程中被修改的card将会被重置为dirty
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.1 ms]
确保巨型对象可以被回收、释放该巨型对象所占的分区,重置分区类型,并将分区还到free列表,并且更新空闲空间大小。
      [Free CSet: 0.0 ms]
将要释放的分区还回到free列表。
   [Eden: 1024.0K(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 6268.2K(10.0M)->1173.0K(10.0M)]
(1)当前新生代收集触发的原因是Eden空间满了,分配了1024.0K,使用了1024.0K;(2)所有的Eden分区都被疏散处理了,在新生代结束后Eden分区的使用大小成为了0.0B;(3)Eden分区的大小还是1024.0K
由于年轻代分区的回收处理,survivor的空间从1024.0K涨到1024.0K ;
在本次垃圾收集活动开始的时候,堆空间整体使用量是6268.2K,堆空间的最大值是10.0M;(2)在本次垃圾收集结束后,堆空间的使用量是1173.0K,最大值保持不变10.0M。
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
user:垃圾收集线程在新生代垃圾收集过程中消耗的CPU时间,这个时间跟垃圾收集线程的个数有关,可能会比real time大很多;
sys:内核态线程消耗的CPU时间 -real=0.03:本次垃圾收集真正消耗的时间

混合gc mixed gc

在并发收集阶段结束后,你会看到混合收集阶段的日志,如下图所示,该日志的大部分跟之前讨论的新生代收集相同,只有第1部分不一样:GC pause(G1 Evacuation Pause)(mixed),0.0129474s,这一行表示这是一个混合垃圾收集周期;在混合垃圾收集处理的CSet不仅包括新生代的分区,还包括老年代分区——也就是并发标记阶段标记出来的那些老年代分区。

并发标记过程

0.195: [GC concurrent-root-region-scan-start]
0.196: [GC concurrent-root-region-scan-end, 0.0004079 secs]
0.196: [GC concurrent-mark-start]
0.197: [GC concurrent-mark-end, 0.0015047 secs]
0.197: [GC remark 0.197: [Finalize Marking, 0.0004369 secs] 0.198: [GC ref-proc, 0.0001929 secs] 0.198: [Unloading, 0.0004440 secs], 0.0013830 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.199: [GC cleanup 5264K->5264K(10M), 0.0007700 secs]
  1. 初始标记阶段 stw
    也是标记GCroot直接引的对象和所在Region,但是与CMS不同的是,这里不止标记O区。注意初次标记一般和YGC同时发生,利用YGC的STW时间,顺带把这事给干了。日志格式如下
    [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0062656 secs]
  2. 根区间扫描阶段
    RootRegion扫描,扫描GCroot所在的region到Old区的引用。日志格式
    0.195: [GC concurrent-root-region-scan-start]
    0.196: [GC concurrent-root-region-scan-end, 0.0004079 secs]
  3. 并行标记阶段
    并发标记,类似CMS,但是标记的是整个堆,而不是只有O区。这期间如果发现某个region所有对象都是'垃圾'则标记为X。日志格式
    0.196: [GC concurrent-mark-start]
    0.197: [GC concurrent-mark-end, 0.0015047 secs]
  4. 重新标记阶段stw
    重新标记,类似CMS,但也是整个堆,并且上一步中的X区被删除。另外采用了初始标记阶段的SATB,重新标记的速度变快。日志格式
  5. 清除阶段stw
    复制/清理,选择所有Y区reigons和'对象存活率较低'的O区regions组成Csets,进行复制清理
    将没有任何存活对象的分区回收,梳理RSet,将没有引用的分区加入可用队列。
    [Times: user=0.00 sys=0.00, real=0.00 secs]
    0.199: [GC cleanup 5264K->5264K(10M), 0.0007700 secs]

问题6:

  • RootRegionScan这个阶段是干嘛的?
    标记出RootRegion指向O区的region,标记这些region是为了降低并发标记的扫描范围,因为并发标记需要扫描GCROOT引用或间接的所有对象,而这些对象一定是在RootRegion出发指向的Region中的。MIXGC中Y区本来就要全扫,所以这里再按照O区过滤下,这样就缩小了扫描范围。该阶段的操作为遍历O区region查询Rset是否有来自RootRegion的,(RootRegion是初始标记得到的)。
  • Rset作用有哪些?
    上题中的作用是一个,还有个作用是YGC时,O区不GC因而认为O区全为‘GCroot’,需扫描全部O区。有了Rset只需要查看所有Y区region的Rset就知道被哪些O区region跨带引用了,避免了扫描整个O区。
  • G1提高效率的点有哪些?
    1 重新标记时X区域直接删除。
    2 Rset降低了扫描的范围,上题中两点。
    3 重新标记阶段使用SATB速度比CMS快。
    4 清理过程为选取部分存活率低的Region进行清理,不是全部,提高了清理的效率。
  • 对比CMS,有哪些不同?
    1 region化的内存结构,采用复制清理的方式,避免了内存碎片。但是这种清理也造成了STW。
    2 SATB速度更快。
    3 初始标记,并发标记,重新标记,清理垃圾四个阶段很像,但是G1中有很多标记region的操作,并借助Rset进行了范围的缩小,提高了并发标记的速度。小结下就是初始标记和YGC的STW一起了,提高了效率;并发标记因为rset的设计,扫描范围缩小了,提高了效率;重新标记因为使用了SATB提高了效率;清理虽然造成了STW,但是复制使内存紧凑,避免了内存碎片。同时只清理垃圾较多的region,最大限度的降低了STW时间。

Full GC问题

E S 和 Old 区没有足够的空间容纳所有的存活对象,就会引起full gc
G1没有fullGC概念,需要fullGC时,调用serialOldGC进行全堆扫描,只有在垃圾回收处理不过来(或者主动触发)时才会出现, G1的Full GC就是单线程执行的Serial old gc,会导致非常长的STW,是调优的重点,需要尽量避免Full GC,常见原因如下:

  1. 全局并发标记期间老年代空间被填满(并发模式失败)
  2. Mixed GC期间老年代空间被填满(晋升失败)
  3. Young GC时Survivor空间和老年代没有足够空间容纳存活对象
解决:类似CMS,常见的解决是:

增大-XX:ConcGCThreads=n 选项增加并发标记线程的数量,或者STW期间并行线程的数量:-XX:ParallelGCThreads=n
减小-XX:InitiatingHeapOccupancyPercent 提前启动标记周期
增大预留内存 -XX:G1ReservePercent=n ,默认值是10,代表使用10%的堆内存为预留内存,当Survivor区域没有足够空间容纳新晋升对象时会尝试使用预留内存

  • Collect Set
    Collect Set(CSet)是指,在Evacuation阶段,由G1垃圾回收器选择的待回收的Region集合。G1垃圾回收器的软实时的特性就是通过CSet的选择来实现的。对应于算法的两种模式fully-young generational mode和partially-young mode,CSet的选择可以分成两种:
    fully-young generational mode下:顾名思义,该模式下CSet将只包含young的Region。G1将调整young的Region的数量来匹配软实时的目标;
    partially-young mode下:该模式会选择所有的young region,并且选择一部分的old region。old region的选择将依据在Marking cycle phase中对存活对象的计数。G1选择存活对象最少的Region进行回收。
  • Remember Set和Card Table
    在G1回收器里面,RS被用来记录从其他Region指向一个Region的指针情况。因此,一个Region就会有一个RS。这种记录可以带来一个极大的好处:在回收一个Region的时候不需要执行全堆扫描,只需要检查它的RS就可以找到外部引用,而这些引用就是initial mark的根之一。Card Table卡表就是RS模型的一种实现
    如果一个线程修改了Region内部的引用,就必须要去通知RS,更改其中的记录。为了达到这种目的,G1回收器引入了一种新的结构,CT(Card Table)——卡表。每一个Region,又被分成了固定大小的若干张卡(Card)。每一张卡,都用一个Byte来记录是否修改过
    【美团解释】
    全称是Remembered Set,是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。还有一种数据结构也是辅助GC的:Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。 逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index

Garbage Collection Cycle

image.png

The following list describes the phases, their pauses and the transition between the phases of the G1 garbage collection cycle in detail:
Young-only phase: This phase starts with a few Normal young collections that promote objects into the old generation. The transition between the young-only phase and the space-reclamation phase starts when the old generation occupancy reaches a certain threshold, the Initiating Heap Occupancy threshold. At this time, G1 schedules a Concurrent Start young collection instead of a Normal young collection.
Concurrent Start : This type of collection starts the marking process in addition to performing a Normal young collection. Concurrent marking determines all currently reachable (live) objects in the old generation regions to be kept for the following space-reclamation phase. While collection marking hasn’t completely finished, Normal young collections may occur. Marking finishes with two special stop-the-world pauses: Remark and Cleanup.

  • Remark: This pause finalizes the marking itself, performs global reference processing and class unloading, reclaims completely empty regions and cleans up internal data structures. Between Remark and Cleanup G1 calculates information to later be able to reclaim free space in selected old generation regions concurrently, which will be finalized in the Cleanup pause.
  • Cleanup: This pause determines whether a space-reclamation phase will actually follow. If a space-reclamation phase follows, the young-only phase completes with a single Prepare Mixed young collection.

Space-reclamation phase: This phase consists of multiple Mixed collections that in addition to young generation regions, also evacuate live objects of sets of old generation regions. The space-reclamation phase ends when G1 determines that evacuating more old generation regions wouldn't yield enough free space worth the effort.

After space-reclamation, the collection cycle restarts with another young-only phase. As backup, if the application runs out of memory while gathering liveness information, G1 performs an in-place stop-the-world full heap compaction (Full GC) like other collectors.

g1 gc 参数

-XX:MaxGCPauseMillis=200 The goal for the maximum pause time.
-XX:GCPauseTimeInterval 最大暂停时间间隔的目标。默认情况下G1不设置任何目标,允许G1在极端情况下连续执行垃圾收集。
-XX:ParallelGCThreads 在垃圾收集暂停期间用于并行工作的最大线程数。这是由VM以以下方式运行的计算机的可用线程数派生出来的:如果进程可用的CPU线程数小于或等于8,则使用该线程数。否则,将大于最终线程数的线程的八分之五相加。
-XX:ConcGCThreads 用于并发工作的最大线程数。默认情况下,这个值是-XX:ParallelGCThreads除以4。
-XX:InitiatingHeapOccupancyPercent=45 并发标记开始的阈值,默认值是堆内存的45%
-XX:G1HeapRegionSize 默认region大小,最小1M,最大32M
-XX:G1NewSizePercent=5 默认年轻代在堆中的比例,最大为-XX:G1MaxNewSizePercent=60
那什么时候发生Mixed GC呢?其实是由如下参数控制着的,另外也控制着哪些老年代Region会被选入CSet。
-XX:G1HeapWastePercent=5 在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC
-XX:G1MixedGCCountTarget=8 一个混合收集周期中包含多少次混合收集
-XX:G1MixedGCLiveThresholdPercent=85 Old generation regions with higher live object occupancy than this percentage aren't collected in this space-reclamation phase.old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet进行回收
参考
三色标记和内存屏障
https://github.com/sunwu51/notebook/blob/master/19.09/java_jvm%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.md
https://www.jianshu.com/p/aef0f4765098
https://zhuanlan.zhihu.com/p/101959252
https://www.jianshu.com/p/c79c5e02ebe6
https://www.cnblogs.com/wade-luffy/p/6053857.html
https://www.jianshu.com/p/aef0f4765098
https://juejin.im/post/5d33be9d5188253a2e1b8fa6
https://juejin.im/post/5b6b986c6fb9a04fd1603f4a
https://www.cnblogs.com/laoqing/p/9738872.html
https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector.html#GUID-6D6B18B1-063B-48FF-99E3-5AF059C43CE8

你可能感兴趣的:(G1 gc 分析)