07-CMS_G1
allocation 英 [ˌæləˈkeɪʃn] n. 拨给的场地;分配的东西;划;拨;分配
evacuation 英 [ɪˌvækju'eɪʃ(ə)n] n. 疏散;撤离;后撤;抽空;排气;排泄
humongous 英 [hjuːˈmʌŋɡəs] adj. 巨大的;庞大的
mutator 英 [mju(ː)ˈteɪtə] n. 变异子;增变基因
1. CMS
- JDK8,G1已经很完善了。JDK9为默认的。JDK14中CMS已经删掉了
- JVM-和CMS一起GC一次-STW-GCRoots-安全点(区)-三色标记
问题:简述CMS的GC过程
- Concurrent_Mark_Sweep
- 特点:追求最短的停顿时间
- SafePoint(安全点(区))Obj引用关系不会再发生变化
- 安全区,线程正在休眠,一定区域不变
- STW(stop_the_world)。业务线程全停
1. procedure
- 初始标记。标记GC_root及其关联的Obj。GC_Root,进行可达性分析,找到的根结点(7个,面试5个即可)
- JVM_stack引用Obj
- Java_native引用Obj
- 方法区中类静态属性引用Obj
- 方法区中常量引用Obj
synchronized
引用Obj- 类元相关
- JMXBean
- 并发标记(三色标记)。以灰色为波峰向前推进过程。只是对Obj间关系进行记录
- 黑色Obj,父引用断开(浮动垃圾)
- 不管即可,下次自动回收
- 黑色Obj,添加子引用
- 额外记录
- 灰白引用断开,添加黑白引用
- 黑Obj重新扫描(CMS)
- 记录灰白引用,继续扫描(G1)
- 黑色Obj,父引用断开(浮动垃圾)
- 重新标记
- 对并发标记进行修正
- 并发清理
2. G1
Garbage first => 垃圾优先
1. procedure
- 初始标记*
- 并发标记
- 最终标记*
- 筛选回收*
2. G1 & CMS
- Heap的设计思想
- CMS:连续的内存划分
- G1:通过Region划分。停顿时间不超过n(用户定义n)。n小,每次回收Region少;n大,每次回收Region多
- G1不会产生空间碎片
- CMS是Mark_Sweep
- G1是整体Mark_Compact,局部是Copying
- G1卡表维护比CMS复杂
- G1每个Region都有卡表,CMS全局只有一个卡表
3. OOM & FullGC排查
- FullGC和OOM排查思路
- JVM最终呈显给我们的就是OOM、FullGC
- 打印日志
- 获取dump文件
- MAT分析
- 修改验证
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-Xmx20m
-Xms20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpBeforeFullGC
-XX:+HeapDumpAfterFullGC
-XX:+HeapDumpPath=C:\Users\lixiang\Desktop\gc
public class GcDemo {
static class OOMObj {
}
public static void main(String[] args) {
ArrayList<OOMObj> list = new ArrayList<>();
while (true) {
list.add(new OOMObj());
}
}
}
4. G1详解
STW:Stop-The-World
1. Feature
- 响应时间优先,适合吞吐量不需要很高,对STW进行控制
- 期望停顿时间:
-XX:MaxGCPauseMillis
根据STW时间,动态调整Y区大小,改变比例 - YGC具有STW,并行,通过Copying实现内存整理
- MixedGC具有并发标记,逐步压缩的特点,并且Old回收前需要先进行一次年轻代的回收
The Garbage First Garbage Collector (G1 GC) is the low pause, server-style generational garbage collector for Java HotSpot VM. The G1 GC uses concurrent and parallel phases to achieve its target pause time and to maintain good throughput. When G1 GC determines that a garbage collection is necessary, it collects the regions with the least live data first (garbage first).
- 入门文章
- G1是服务端应用使用的GCtor,目标是用在多核、大内存的机器上。大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量
2. 内存分代(Region)
1. 传统
传统的GC将连续内存划分为新生代、Old和永久代(JDK 8去除了永久代,引入了元空间Metaspace)
2. G1
- G1的各代存储地址是不连续内存,每一代都为n个不连续的大小相同的Region。每个Region可能是新生代或Old,同一时刻只能属于某代
- Region可以说是G1一次回收的最小单元。不连续,逻辑分代。即每一次回收都回收N个Region
- G1尽可能回收垃圾最多的Region。与此同时,G1会维护一个空Region的链表
- 新生代并不适用这种算法,依然是新生代满了,对整个新生代进行回收(整个新生代的Obj,要么被回收、要么晋升)。新生代也采取分区机制,这样跟Old的策略统一,方便调整代大小
3. CSet
- Collection_Set
- 背景:记录GC要收集的Region集合,集合里的Region可以是任意年代的
- 在CSet中存活的数据,会在GC中被移动到另一个可用分区,CSet中的分区可以来自Eden、Survivor、Old
- CSet占用不到Heap的1%大小
4. Card_Table
- jvm的card table数据结构
- points-out结构(我引用了谁的Obj)
- 背景:由于做YGC时,需要扫描整个Old,效率非常低
- 实现:如果Old区一个Obj指向Y区,在Card_Table中标记为1。结构上,Card_Table用BitMap实现
- Card_Table是由元素为1B的数组来实现的,数组里的元素称之为卡片/卡页(Page)。Card_Table会映射到整个堆空间,每个卡片对应堆中的512B
- eg:一个大小为1GB的堆,那么Card_Table的长度为2097151 (1GB/512B);每个Region大小为1MB,每个Region都会对应2048个Card_Page
查找一个Obj所在的CardPage只需要简单的计算就可以得出:(Obj的地址 - 堆开始地址)/512
5. RSet
- Remembered_Set
- points-into结构(谁引用了我的Obj)
- 一个HashTable
- Key:引用方Region的起始地址
- Value:一个集合,元素为Card_Table的Index
细节:
- 年轻代RS,只保存Old的引用。Old回收所有Young内部的Obj引用关系都会被扫描,所以RS不需要保存来自Young内部的引用
- OldRS,只保存Old的引用。Old回收之前会先进行Young的回收,Young回收后Eden区变空了,G1会在Old回收过程中扫描Survivor到Old的引用
- 实现:G1维护了一个Dirty_Card_Queue。对于应用程序的引用赋值语句
object.field = object
,JVM会在之前和之后执行特殊操作,在Dirty_Card_Queue中入队一个保存了Obj引用信息的card。Young回收时,G1会对Dirty_Card_Queue中所有的card进行处理,以更新RS,保证RS实时准确的反映引用关系
1. RSet + Card_Table
eg:Region_B中的Obj_b引用了Region_A中的Obj_a,这个引用关系跨了两个区域。b的CardPage为122,Region_A的RSet中,以Region_B的地址作为key,b所在CardPage_index为value记录了这个引用关系,这样就完成了跨区域引用记录
- CardTable粒度有点粗(512B),在一个CardPage内可能会存在多个Obj。所以在扫描标记时,需要扫描RSet中关联的整个CardPage
- 通过CardTable与RSet,可以快速的找到当前Region的GC_Root,不仅提高GC_Root的效率,并且可以实现独立Region进行回收,而不受其他Region引用关系的干扰
6. Procedure
- 初始标记(initial_mark)STW
- GC Roots,入扫描栈(marking_stack),修改TAMS的值,与YGC共享STW
- 并发标记(Concurrent_Marking)
- 三色标记
- SATB + TAMS解决新Obj产生、Obj消失问题
- 扫描SATB(pre-write barrier)记录的引用,完善SATB
- 重新标记(Remark)STW
- 处理漏标(STAB算法)非常轻量,只需要flush SATB pre-write barrier的buffer,完善snapshot
- 清除垃圾(Cleanup)STW
- 回收百分之百为垃圾的内存分段
- 统计Region中,BitMap里被标记为存活Obj有多少
1. YGC(STW)
- Eden用尽时开始回收。和
initial_mark
一起做 - G1创建回收集(Collection_Set),所有Young_Region进入CSet
- 以RSet作为根集扫描获取存活Obj,将
Eden/Survivor
中的存活Obj拷贝、合并到一个新Region里(Evacuation)减少内存碎片- 更新RS,处理RS(处理引用)。处理
dirty card queue
中的card - 扫描根,复制Obj(处理引用)
- GC_root
- 线程栈变量
- 静态变量
- 常量池
- JNI指针
- Object_Graph被遍历,Eden中存活的Obj会被复制到Survivor(无碎片),Survivor存活Obj如果年龄未达阈值,年龄会加1,达到阈值被复制到Old中(无碎片)
- GC_root
- 更新RS,处理RS(处理引用)。处理
2. Concurrent_Marking
- Young的空间占用发生了变化——在并发收集周期中,至少有一次(很可能是多次)新生代GC
- 注意到一些分区被标记为X,这些分区属于Old,它们就是标记周期找出的包含最多垃圾的分区(它们内部仍然保留着数据)
- 识别出所有空闲的分区、RSet梳理、将不用的类从metaspace中卸载、回收巨型Obj
1. 三色标记
并不是真正的在Obj上进行了标记,通过Region中BitMap实现
- 黑色:Region内BitMap被标记的Obj
- 灰色:扫描到的进入Stack中的Obj
- 白色:没有被扫描到的Obj
2. SATB
《Snapshot-At-The-Beginning》
- 背景:解决Obj消失问题(灰Obj取消引用)
- 缺点:被替换的白Obj就是要被收集的垃圾,这次的标记会让它躲过GC,浮动垃圾(float_garbage)
- 相当于Concurrent_Marking开始时给Obj图(Object Graph)拍了快照,Obj图记录在next_marking_bitmap,本次GC只针对(Object Graph)的垃圾Obj。Obj引用断开时,将引用加入到log_buffer,有线程不断三色log_buffer完善ObjTree。Remark阶段会清空log_buffer,达到(Object_Graph)快照的目的
3. TAMS
- 《top-at-mark-start》
- 背景:解决新Obj产生问题(黑Obj增加引用)
- Region中有两个TAMS指针,分别为prevTAMS和nextTAMS。(Concurrent_Marking)在TAMS和TOP间的Obj是新分配的,不会记录到BitMap中,GC只回收BitMap上的垃圾
- Bottom:指向Region起点
- Top:当前Region分配Obj的游标,Top永远指向当前Region最新分配的Obj
- PrevTAMS和NextTAMS:分别标记前后两次并发标记周期开始时Top指针的位置
- End:表示Region终点
- 两个BitMap,G1是借助BitMap来存放Obj存活标记,bit表示每个Region中的某个Obj的起始地址,如果bit标记为1,则表示该Obj存活,bit与Obj对应有一套算法
- SATB(Snapshot-At-The-Begin)
- initial_mark开始时,G1打了一个快照,形成一个所谓的Obj图(Object Graph)。这个Obj图记录在《next marking bitmap》之中
- 在Concurrent_Marking会在这个BitMap中记录Obj存活标记
- 最终Remark结束后,完成对快照Obj图所有标记
- GC周期只关注BitMap中的,新new的Obj并没有在BitMap中
- 进入到清理阶段,《next marking bitmap》与《previous marking bitmap》会发生置换(swap),《next marking bitmap》在下一次周期开始前会被清空。那么此时这个Region的《previous marking bitmap》可以直接表示出,该Region在
[Bottom, NextTAMS)
区间内存活Obj数量,并且可以根据BitMap算出存活Obj的具体地址,辅助下一步的Evacuation(选取CSet,拷贝并合并存活Obj到新的Region里)。回收的同时减少了内存碎片,当然Evacuation也是STW的
- CMS处理
- 《Incremental Update》当一个白色Obj被一个黑色Obj引用,将黑色Obj重新标记为灰色,让GCtor重新扫描
- 特点:
- 所有在并发标记过程中,由黑变灰的Obj都需要重新遍历标记
- 性能开销大,小内存时适用
3. Mixed_GC(STW)
- 堆内存使用达到一定值(默认45%)
- Young、Old会同时被回收,会将该分区中存活的数据拷贝到另一个分区
- Old中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来,Mixed_GC会进行很多次
4. FGC(STW)
Mixed_GC实在无法跟上程序分配内存的速度,导致Old填满无法继续进行Mixed_GC,就会使用SerialOld(Full_GC)来收集整个Heap。G1是不提供Full_GC的
6. G1 & CMS
- 相较于CMS,G1还不具备全方位压倒性优势
- eg:在用户线程运行过程中,G1无论是为了GC产生的内存占用,还是程序运行时的额外执行负载都要比CMS要高
- 通常情况下,在小内存的应用上CMS的表现大概率上会优于G1,G1在大内存应用上会发挥其最大的效率,两者的性能平衡点在6-8g内存之间
1. Heap分配不同
- CMS将堆逻辑上分成Eden、Survivor(S0, S1)、Old,并且是固定大小,连续的内存块
- G1将堆分成多个大小相同的Region,默认2048个,在1Mb到32Mb之间大小。逻辑上分成Eden、Survivor、Old、巨型、空闲,不是固定大小,会根据每次GC的信息做出调整
2. 并发标记阶段
三色标记算法处理结果不同
- CMS在三色标记算法阶段,如果将白色Obj重新分配给黑色Obj时,分配期间采用增量更新方式(写屏障中发现白色Obj引用被分配给黑色Obj时,分配过程中将黑色重新设置为灰色,即插入的时候就记录修改)
- G1在三色标记算法阶段,如果将白色Obj重新分配给黑色Obj时,并发标记阶段(采用SATB),所有被改变的Obj入队,在写屏障中统一处理为灰色
3. 压缩策略不同
- CMS中不启用压缩会产生很多内存碎片,没有空间来分配新增Obj。设定参数合并相邻的的空闲内存,当合并超过一定次数后触发Full GC进行压缩
- G1每次回收过程中,将多个Region拷贝到空闲Region时都会进行压缩
4. 可预测停顿
相比CMS,G1可以设定每次GC的时间,从而让GC在规定时间内回收效益最大的内存
5. GC策略不同
- CMS中,GC的策略分为YGC、Old GC、Full GC
- G1中,GC的策略分为YGC、Mixed GC、Full GC
6. Young GC不同
- CMS使用分代回收,堆被分成了年轻代、Old,其中年轻代回收依赖ParNew,需要STW
- 将Eden、Survivor中存活的Obj复制到另一个Survivor,每次GC一次依然存活的Obj的age都+1,默认age=15时(经历多次Young GC)那么就会晋升到Old,然后清空Eden、Survivor
- G1的Young GC是自己去清理的,而不是并行GC处理
- 将Eden中存活的复制到Survivor中去,符合条件的晋升,如果Survivor空间不够,那么将Survivor、Eden中存活的数据一起复制到一个新的Survivor
- 每次Young GC之后,根据之前一次GC的信息调整Eden、Survivor大小,进行动态分配
7. log
1. CMS
使用多少比例的Old后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小(频繁CMS回收)
java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC T06_FullGC_Problem
注意GC日志中是否有 promotion failed
和 concurrent mode failure
两种状况,可能会触发Full GC
promotion failed
:进行Minor_GC时,Survivor space放不下、Obj只能放入Old,而此时Old也放不下造成的concurrent mode failure
:执行CMS过程中,同时有Obj要放入Old,而此时Old空间不足(当前浮动垃圾过多导致暂时性的空间不足触发Full GC)
1. PerNew(young)
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew
:年轻代收集器(Allocation Failure)
:失败原因6144K->640K
:收集前后的对比(6144K)
:整个年轻代容量6585K->2770K
:整个堆的情况(19840K)
:整个堆大小
2. CMS(old)
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
// 8511 (13696) :Old占用(最大)
// 9866 (19840) :整个Heap占用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
// 这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// 标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// STW阶段,YG occupancy:年轻代占用及容量
// [Rescan (parallel):STW下的存活Obj标记
// weak refs processing:弱引用处理
// class unloading:卸载用不到的class
// scrub symbol(string) table:
// cleaning up symbol and string tables which hold class-level metadata and
// internalized string respectively
// CMS-remark: 8511K(13696K):阶段过后的Old占用及容量
// 10108K(19840K):阶段过后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
// 标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// 重置内部结构,为下次GC做准备
2. G1
Garbage First Garbage Collector Tuning
java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseG1GC com.listao.jvm.gc.T15_FullGC_Problem01
G1根据STW时间,动态调整young大小
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
// young -> 年轻代 Evacuation -> 复制存活Obj,去另一个Region
// initial-mark 混合回收阶段,这里是YGC混合Old回收
[Parallel Time: 1.5 ms, GC Workers: 1] // 一个GC线程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 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: 18.8M(20.0M)->18.8M(20.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
// 以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
// 无法evacuation,进行FGC,正常情况下不应该有FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]