同步操作将从 古春波/java-construct 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
先介绍两个概念:吞吐量和响应能力,响应能力和吞吐量是评价一个系统的两个重要指标
吞吐量
- 吞吐量关注的是,在一个指定的时间内,最大化一个应用的工作量。
- 如下方式来衡量一个系统吞吐量的好坏:
- 在一定时间内同一个事务(或者任务、请求)完成的次数(tps)
- 数据库一定时间以完成多少次查询
- 对于关注吞吐量的系统,一定次数卡顿(即stw)是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单次快速的响应并不值得考虑
响应能力
响应能力指一个程序或者系统对请求是否能够及时响应,比如:
- 一个桌面UI能多快地响应一个事件
- 一个网站能够多快返回一个页面请求
- 数据库能够多快返回查询的数据
对于这类对响应能力敏感的场景,长时间的停顿是无法接受的,关注每一次反应的能力。
与应用线程同时工作,几乎不需要 stop the work(与CMS类似);
G1的设计规划是要替换掉CMS,G1在某些方面弥补了CMS的不足,比如CMS使用的是mark-sweep算法,自然会产生内存碎片(CMS只能在Full GC时,用 stop the world整理内存碎片),然而G1基于copying算法,高效的整理剩余内存,而不需要管理内存碎片
GC停顿更加可控,甚至可以设置一个时间阈值,比如说可以回收某些部分的老年代,而不像CMS老年代必须全部回收(其它收集器时间可能难以把握)
G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题—G1将整个堆分成相同大小的分区或称为区域( Region)
收集集合(Collection Set):一组可被回收的分区的集合,在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、 survivor空间、或者老年代,并且可以同时包含这几代分区的内容。
已记忆集合(Remember Set):RSet使区域Region的并行和独立成为可能,RSet记录了其他Region中的对象引用本 Region中对象的关系,属于 points-into结构(谁引用了我的对象)。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。
G1 GC是在 points-out 的 card table之上再加了一层结构来构成 points-into Rset:每个Region会使用point-into Rset记录下到底哪些别的Region有指向自己的指针,而这些指针分别在哪些card的范围内【也就是说 card table 记录自己指向了谁,Point-into Ret 记录了谁指向了自己】。这个RSet其实是一个 hash table,key是别的Region的起始地址,value是一个集合,里面的元素是 card table的 Index,举例来说,如果region A的Rset里有一项的key是 region B,value里有 index为1234的card,它的意思就是 region B的一个card里有引用指向region A。所以对 region A来说该RSet记录的是 points-into的关系,而 card table仍然记录了 points-out的关系。
card table是什么又是怎么来的呢?如果一个对象引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1中又引入了另外一个概念,卡表( Card Table)。一个 Card Table将一个分区在逻辑上划分为固定大小的连续区域每个区域称之为卡。卡通常较小,介于128到512字节之间。 Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址
下图表示了RSet、Card和Region的关系):
上图中有三个Region,每个Region被分成了多个Card,在不同Region中的Card会相互引用,Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points- into。
而维系RSet中的引用关系靠post-write barrier和Concurrent refinement threads来维护,操作伪代码如下:
void oop_field_store(oop* field, oop new_value) {
pre_write_barrier(field); // pre-write barrier: for maintaining SATB invariant
*field = new_value; // the actual store
post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference
}
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的工作量。
Snapshot-at-the-beginning(SATB)SATB是G1 GC在全局并发标记阶段使用的增量式的标记算法。并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区
G1提供了两种GC模式, Young GC和 Mixed Gc两种都是完全 Stop The World的
Young GC:将选择所有年轻代里的 Region 进行GC(所以CSet里存的就是所有年轻代里面的Region)。将存活的对象复制到幸survivor区是多线程执行的并且需要stw,如果满足晋升到老年代阈值,则某些对象将被提升到老年代,并通过动态计算出下次GC时堆中年轻代的 Region个数,即年轻代内存大小来控制 Young GC的时间开销。
Young GC在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空白即不属于任何一个分区(Eden、 Survivor、Old)
Mixed GC:将选择所有年轻代里的 Region,外加根据 global concurrent marking统计得出收集收益高的若干老年代 Region进行GC(所以CSet里存的是所有年轻代里的 Region加上在全局并发标记阶段标记出来的收益高的 Region)。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region,以此来控制Mixed GC的时间开销。
Mixed gc 中的 global concurrent marking的执行过程类似于CMS,但是不同的是,在G1 GC中,它主要是为 Mixed GC提供标记服务的【老年代之所以知道为什么哪里的回收效益高就是根据此服务得出的结果确定的】,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤
触发时机由一些参数控制,另外这些参数也控制着哪些老年代 Region 会被选入CSet(收集集合)
G1算法将堆划分为若干个区域( Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代和老年代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在
YGC收集时如何找到根对象呢?老年代所有的对象都是根对象吗?那如果直接对整个老年代进行扫描的话会耗费大量的时间。于是G1引入了我们上面提到的RSet的概念,作用是跟踪指向某个heap区内的对象引用。但是在新生代之间记录引用吗?这是不必要的,原因在于每次GC时所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。YGC收集过程如下:,不懂的原因,对Rset 和point out point into 相关还是不理解
这是Mixed gc 中的Global concurrent marking中的内容,提到并发标记,我们不得不了解并发标记的三色标记算法,mark的过程就是遍历heap标记 live object的过程。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性。我们将对象分成三种类型:
这时候应用程序执行了以下操作A.c=C和B.c=null这样,对象的状态图变成如下情形
这时候垃圾收集器再去标记扫描的时候就会变成下图这样,这样就会造成漏标:
SATB snapshot at the beginning
SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成个对象图。在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象
如何找到在GC过程中分配的对象呢? 这是收集过程中可能遇到的第二个问题,每个Region记录着两个top-at-mark- start(TAMS)指针,分别为 prevtamst和 next AMS。在TAMS以上的对象就是新分配的,因而被视为隐式 marked。通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象
解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢?Write Barrier就是对引用字段进行赋值做额外处理。通过 Write Barrier就可以了解到哪些引用对象发生了什么样的变化。
SATB仅仅对于在 marking开始阶段进行snapshot"(marked all reachable at markstart)),但是 concurrent的时候并发修改可能造成对象漏标记
漏标与误标
通过 XX. MaxGcPauseMillis=x 可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择Cset来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成 Full gc。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。
G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小即可
Evacuation Failure【Evacuation是拷贝存活对象的意思,是GC步骤之一,GC步骤分为两步全局并发标记(global concurrent marking)和拷贝存活对象(evacuation)】 类似于CMS里面的晋升失败堆空间的垃圾太多导致无法完成 Region之间的拷贝,于是不得不退化成 Full GC来做一次全局范围内的垃圾收集
对比其它收集器
- G1 VS CMS对比使用mark- sweep的CMS,G1使用的copying算法不会造成内存碎片
- 对比 Parallel Scavenge(基于 copying)Parallel Old收集器(基于mark- compact-sweep), Parallel会对整个区域做整理导致gc停顿会比较长,而G1只是特定地整理压缩某些Region
- G1并非一个实时的收集器,与 parallel Scavenge一样,对gc停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的gc停顿时间。与之前的gc收集器对比,G1会根据用户设定的gc停顿时间智能评估哪几个 region需要被回收可以满足用户的设定
2020-03-03T11:06:20.055+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0024128 secs]
[Parallel Time: 1.3 ms, GC Workers: 8]
# GC Workers: 8 ,当前的GC线程数为8
[GC Worker Start (ms): Min: 178.9, Avg: 178.9, Max: 178.9, Diff: 0.1]
# GC线程开始
[Ext Root Scanning (ms): Min: 0.3, Avg: 0.4, Max: 0.5, Diff: 0.3, Sum: 3.2]
# 根节点扫描
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
# 更新RSet
[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.6, Avg: 0.7, Max: 0.8, Diff: 0.2, Sum: 5.8]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
# 处理引用队列
[Termination Attempts: Min: 1, Avg: 6.0, Max: 9, Diff: 8, Sum: 48]
# 此以上的步骤都是和之前说到的YGC的处理流程是一一对应的
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 0.9]
[GC Worker Total (ms): Min: 1.2, Avg: 1.2, Max: 1.3, Diff: 0.1, Sum: 10.0]
[GC Worker End (ms): Min: 180.2, Avg: 180.2, Max: 180.2, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
# 即clear CartTable
[Other: 1.0 ms]
[Choose CSet: 0.0 ms]
# 选择要回收的Region
[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: 3990.1K(10.0M)->2847.6K(10.0M)]
# 表示执行完YGC之后,整个eden空间的结果,可以看到执行完之后eden空间变成了0;Survivor空间变成1m,这是从新生代来的
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-03T11:06:20.058+0800: [GC concurrent-root-region-scan-start]
2020-03-03T11:06:20.059+0800: [GC pause (G1 Humongous Allocation) (young)2020-03-03T11:06:20.059+0800: [GC concurrent-root-region-scan-end, 0.0011467 secs]
2020-03-03T11:06:20.059+0800: [GC concurrent-mark-start]
, 0.0019177 secs]
# 以上是并发的一些处理
[Root Region Scan Waiting: 0.5 ms]
[Parallel Time: 0.9 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 182.5, Avg: 182.6, Max: 182.8, Diff: 0.3]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 1.2]
[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.4, Avg: 0.4, Max: 0.5, Diff: 0.1, Sum: 3.5]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.4]
[Termination Attempts: Min: 1, Avg: 4.8, Max: 8, Diff: 7, Sum: 38]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 0.4, Avg: 0.7, Max: 0.9, Diff: 0.4, Sum: 5.2]
[GC Worker End (ms): Min: 183.3, Avg: 183.3, Max: 183.4, Diff: 0.1]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.4 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.2 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: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 3912.6K(10.0M)->3895.4K(10.0M)]
# 第二次YGC的结果
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-03T11:06:20.061+0800: [GC concurrent-mark-end, 0.0015466 secs]
2020-03-03T11:06:20.061+0800: [Full GC (Allocation Failure) 3895K->3731K(10M), 0.0042062 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 3895.4K(10.0M)->3731.8K(10.0M)], [Metaspace: 3113K->3113K(1056768K)]
[Times: user=0.03 sys=0.00, real=0.00 secs]
2020-03-03T11:06:20.066+0800: [GC remark, 0.0000313 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-03T11:06:20.066+0800: [GC concurrent-mark-abort]
完成了
Heap
garbage-first heap total 10240K, used 4755K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
# 堆的空间分配情况
Metaspace used 3238K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 352K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。