尼恩说在前面
听说你是高手,说说,你的JVM调优方法论? 说说,何时进行JVM调优?JVM调优的基本原则? 说说,G1 垃圾回收器的底层原理、基本流程、调优过程? 说说,JVM调优量化目标?JVM调优的步骤?
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
本文目录
- 尼恩说在前面
- 首先回答一下: JVM调优方法论
- 第9章:三大GC 组件的技术选型 cms g1 zgc 选型
- 1. CMS(Concurrent Mark-Sweep GC)
- 2. G1(Garbage First GC)
- 3. ZGC(Z Garbage Collector)
- CMS、G1 和 ZGC 选型建议:
- 第10章:cms 底层原理和调优实战
- 第11章:G1 底层原理、基本流程、调优实战
- 11.1. 什么叫 Garbage First 垃圾优先呢?
- 11.2. 什么是G1垃圾回收器
- 11.3. G1的执行流程
- G1 的垃圾回收过程
- 11.4. GC root 可达性分析 与 Remembered Set(记忆集)
-Remembered Set(记忆集)的起因
- 位图--bitMap
- Card Table 卡表
- 分代回收
- 11.5. G1的 Young GC 年轻代垃圾回收(完全年轻代GC)
- 步骤1. 选择收集集合(Choose CSet)
- 步骤2. 扫描根(Root Scanning)
- 步骤3. RSet 记忆集合更新(Update RS )
- 步骤4. RSet 记忆集合扫描(Scan RSet )
- 步骤5. 移动(Evacuation/Object Copy)对象
- 步骤6. 处理引用
- 写入屏障(类似AOP思想的切面函数)
- 步骤7. 结束收集
- G1年轻代回收核心技术总结
- 11.6. G1的Mixed GC混合回收(部分年轻代GC)
- 1. 初始标记
- 2. 并发标记
- 3. 最终标记(Final Mark)
- 深度问题:SATB “浮动垃圾” 怎么来的?
- 4. 清理(Cleanup)
- 5. 转移
- 11.7. G1的Full GC
- 暂停时间的控制
- 年轻代大小的配置
- 11.8. CMS与G1的区别
- 11.9. G1的 配置参考
- 11.10. G1的GC垃圾回收日志解读
- 年轻代GC日志(完全年轻代)
- 老年代垃圾回收(部分年轻代/混合回收)日志
-并发标记日志
-混合回收日志
- 11.11. G1 常用参数
- 1. 堆内存配置 相关参数
- 2. GC配置 相关参数
- 3. NMT 本地内存相关参数
- 4. GC日志配置 相关参数
- 5. 其他 参数
- 11.2. G1 调优实战
- 11.3. 结语
- 说在最后:有问题找老架构取经
首先回答一下: JVM调优方法论
关于JVM调优的方法论,之前写过在线的版本,但是最新版本有调整。
第9章:三大GC 组件的技术选型 cms g1 zgc 选型
1. CMS(Concurrent Mark-Sweep GC)
并发回收:CMS 是并发的标记-清除垃圾回收器,年轻代使用复制算法,老年代使用标记-清除算法。这意味着在垃圾回收时,应用线程与 GC 线程同时工作,从而减少应用停顿时间。 老年代GC触发条件:CMS 不会等到老年代完全满了才进行回收,而是当老年代使用达到一定阈值时(默认 92%)开始回收,以避免长时间的 Full GC。 适用场景:适合对低延迟有较高要求的应用,例如 Web 服务、在线交易系统。
低延迟:由于 CMS 在大部分阶段是并发的,它能大大减少老年代回收时的 "Stop-The-World" 停顿时间。 成熟稳定:CMS 已存在多年,适用于许多生产环境。
内存碎片问题:由于 CMS 是标记-清除回收器,不会整理内存,老年代内存中会产生碎片,这可能导致 Full GC。 并发模式失败:如果老年代在回收过程中无法及时腾出足够空间,可能会发生“Concurrent Mode Failure”,这会退回到单线程的 Serial Old GC,导致长时间暂停。 较高 CPU 消耗:CMS 在回收时需要额外的 CPU 资源,可能对 CPU 密集型应用有较大影响。
2. G1(Garbage First GC)
分区堆模型:G1 将堆分成多个大小相等的区域(Region),不同区域可能属于年轻代或老年代。通过收集垃圾最多的区域进行回收,因此称为“Garbage First”。 混合回收:G1 能够同时回收年轻代和老年代的内存,避免了 Full GC 的大范围内存整理。 暂停时间可控:G1 可以根据设置的最大暂停时间目标(默认 200ms),智能选择要回收的区域,来控制 GC 的影响。
适用于大堆内存:G1 尤其适合大堆内存(通常超过 6GB)环境,能够有效处理较大的老年代回收。 避免 Full GC:通过区域化内存管理和并行收集,G1 几乎避免了传统的 Full GC 停顿。 碎片整理:G1 在回收时会进行内存整理,减少了内存碎片问题。
调优复杂:虽然 G1 能够自适应配置暂停时间,但在高性能应用场景下,G1 的调优相对复杂。 初始性能不及 CMS:在某些场景下,G1 的初始性能可能不如 CMS,尤其是在堆内存较小的情况下。 较高内存消耗:相比 CMS,G1 的元数据管理和并行策略可能导致较高的内存占用。
3. ZGC(Z Garbage Collector)
超低延迟:ZGC 是一种面向超低延迟设计的垃圾回收器,旨在将垃圾回收停顿时间控制在 10ms 以内。 堆内存极大:ZGC 支持非常大的堆内存(TB 级别),这使得它在处理大规模内存应用时有很大的优势。 并发收集:ZGC 采用完全并发的回收策略,垃圾回收和应用线程几乎同时进行,极大减少了暂停时间。
几乎无停顿:ZGC 的最大卖点就是它几乎不会产生明显的 GC 停顿,即使是在处理大内存时。 处理超大堆内存:ZGC 在堆内存非常大的情况下(如超过 1TB),仍然能保持很好的性能表现。 低内存碎片:ZGC 采用了内存指针的标记整理机制,能有效防止碎片问题。
CPU 开销高:ZGC 对 CPU 的要求较高,适合多核环境下使用,否则可能导致较高的 CPU 资源消耗。 尚在发展:相比 CMS 和 G1,ZGC 相对较新(Java 11 引入),尽管表现优异,但成熟度不如 CMS 或 G1。 较高的内存使用:ZGC 在运行时需要额外的元数据,导致整体内存占用较高。
CMS、G1 和 ZGC 选型建议:
如果应用需要超低延迟,且堆内存非常大(如 TB 级别),并且系统有足够的 CPU 核心,ZGC 是最好的选择。 ZGC 几乎不会产生显著的暂停时间,适合高频交易、超大内存服务等需要极致低延迟的场景。 如果应用对延迟有要求,且内存比较大(通常 6GB 以上),并且希望在兼顾延迟和吞吐量之间找到平衡,可以选择 G1。 G1能够提供稳定、可预测的暂停时间,适合大多数服务端应用场景。 如果系统是中等规模的内存(<6GB),并且低延迟重要但 CPU 资源有限,CMS是一个成熟且较为轻量的选择。 尽管 CMS 有内存碎片和并发模式失败的风险,它仍然适合对响应时间要求较高的中小型应用。
第10章:cms 底层原理和调优实战
第11章:G1 底层原理、基本流程、调优实战
11.1. 什么叫 Garbage First 垃圾优先呢?
11.2. 什么是G1垃圾回收器
G1 is a generational, incremental, parallel, mostly concurrent, stop-the-world, and evacuating garbage collector which monitors pause-time goals in each of the stop-the-world pauses.
Eden区域 - 新分配的对象 Survivor区域 - 年轻代GC后存活但不需要晋升的对象
eden region
和 survivor region
,所有新建的对象均创建在eden region
中,在经过young gc后,对象被复制整理到survivor region
中(年龄不够到老年代时);晋升到老年代的对象 直接分配至老年代的大对象,占用多个区域的对象
FHR - Free Heap Region :空闲分区,还未进行分配 YHR - Yound Heap Reagion :年轻代分区 ERH - Eden Heap Region : eden区,伊甸园,放新创建对象 SRH - Survivor Heap Region : Survivor 区,存货去,放每次GC后存活对象 OHR - Old Heap Region : 老年代分区,放长命对象 HHR - Humongous Heap Region : 巨型对象分区,存放巨大(>Region Size 的 50%)对象
-XX:G1HeapRegionSize=4M
进行配置。11.3. G1的执行流程
存活对象超过年龄阈值(默认 15)仍未被回收则进入老年代:熬过一次 GC 增加一岁,默认年龄超过 15 岁还没有被回收则被移动到老年代,通过设置 jvm 参数 -XX:MaxTenuringThreshold
来设置对象进入老年代的阈值;大对象直接进入老年代:超过 G1HeapRegionSize 的一半会被认为是大对象,大对象直接进入老年代; 动态年龄判断:在survivor区中,Survivor区中相同年龄的对象总大小是否超过了Survivor区的一半,这个一半的比例阈值可以通过-XX:TargetSurvivorRatio`值(默认50),如果是,那么这个年龄就是晋升的动态年龄,该年龄及以上年龄的对象都会被直接晋升到老年代。 空间分配担保:young GC 后,survivor 区空间不能容纳全部存活对象, G1 中的对象何 进入老年代
年轻代回收(Young GC)阶段:
在这个阶段,G1垃圾回收器主要回收年轻代(包括Eden区和Survivor区)中的对象。 当Eden区被填满或者达到了某个条件(例如,G1认为回收这些区域的收益较高)时,就会触发一次年轻代回收。 年轻代回收的过程中,存活的对象会从Eden区和Survivor区复制到另一个Survivor区或者直接晋升到老年代。 这个过程通常是Stop-The-World(STW)的,即在回收过程中,应用程序的其他线程会被暂停。
混合回收(Mixed GC)阶段:
当老年代的占用率达到了一定阈值(由 -XX:InitiatingHeapOccupancyPercent
参数控制,默认值为45%),G1会启动混合回收阶段。在这个阶段,G1不仅回收年轻代,还会回收一部分老年代的区域,这些区域被认为含有较多垃圾。 混合回收的目的是减少老年代的内存占用,并释放空间供应用程序使用。 混合回收也是STW的,但G1会尝试在用户指定的停顿时间目标内完成。
G1 的垃圾回收过程
MaxTenuringThreshold
设定的年龄阈值,或者因为它们是大对象(超过某个Region一半大小的对象)而直接分配到老年代。检查老年代空间:在Minor GC之前,G1会检查老年代的可用空间。 处理晋升失败:如果老年代空间不足,G1会尝试找到一个足够大的连续空间来存放晋升的对象。如果找不到,就会触发担保失败。 担保失败处理:担保失败时,G1会进行一次Full GC,以清理整个堆并释放空间。
-XX:G1HeapWastePercent
:设置老年代中可以浪费的内存百分比,默认值通常是5%。当老年代的剩余空间低于这个百分比时,G1会避免进行Mixed GC,以确保有足够的空间进行对象晋升。-XX:G1OldCSetRegionThresholdPercent
:设置在Mixed GC期间要回收的老年代Region的最大百分比。
11.4. GC root 可达性分析 与 Remembered Set(记忆集)
虚拟机栈(栈帧中的本地变量表)中引用的对象。 本地方法栈中JNI(即一般说的Native方法)引用的对象。 方法区中类静态属性引用的对象。 方法区中常量引用的对象。
Remembered Set(记忆集)的起因
位图--bitMap
没有东西,它是空闲的。 有东西,它是使用中的。
注意,这里说Card Table实现RSet,并不是说CardTable是RSet背后的数据结构,只是RSet中存储的是CardTable数据
Card Table 卡表
其中,key是引用了当前region的其他 region 区域的地址。 Value是一个数组,value中的元素是引用方的对象所在内存块儿在CardTable中的下标。
分代回收
完全年轻代GC(fully-young collection),也称年轻代垃圾回收(Young GC) 部分年轻代GC(partially-young collection)又称混合垃圾回收(Mixed GC) Full GC ,退化到 Serial Old收集器。
11.5. G1的 Young GC 年轻代垃圾回收(完全年轻代GC)
步骤1. 选择收集集合(Choose CSet)
步骤2. 扫描根(Root Scanning)
步骤3. RSet 记忆集合更新(Update RS )
步骤4. RSet 记忆集合扫描(Scan RSet )
步骤5. 移动(Evacuation/Object Copy)对象
步骤6. 处理引用
写入屏障(类似AOP思想的切面函数)
def evacuation_write_barrier(obj, field, newobj){
//检查引用和被引用新对象是否在同一个区域
if(!check_cross_ref(obj, newobj)){
return
}
//不重复添加dirty_card
if(is_dirty_card(obj)){
return
}
to_dirty(obj);
//将obj添加到newobj所在region的rs
add_to_rs(obj, newobj);
}
步骤7. 结束收集
G1年轻代回收核心技术总结
11.6. G1的Mixed GC混合回收(部分年轻代GC)
初始标记(Initial Mark):这是一个Stop-The-World(STW)阶段,使用三色标记法来快速标记从GC Root直接可达的对象。这一步确保了回收过程中不会遗漏任何重要的根对象。 并发标记(Concurrent Mark):在此阶段,标记工作与用户线程并发执行。垃圾回收器遍历对象图,对存活的对象进行标记。这个过程可以充分利用多核处理器的并行能力,提高标记效率。 最终标记(Final Mark):再次进入STW阶段,处理与SATB(Snapshot-At-The-Beginning)相关的对象标记。SATB是一种在GC开始时捕获对象图快照的技术,它确保了在并发标记期间新创建的对象也能被正确标记。 清理(Cleanup):另一个STW阶段,清理那些没有任何存活对象的区域。这些区域将被回收,以便后续的内存分配。 转移(Evacuate):最后,将存活的对象从它们的当前区域复制到其他空闲区域。这个过程可能涉及对象的移动和指针的更新,因此也是STW的。
1. 初始标记
黑色:表示当前对象不仅自身在GC Root的引用链上,而且它所引用的所有对象也已经被标记为存活。在位图实现中,黑色对象通过相应的bit位被标识为1。 灰色:表示当前对象在GC Root的引用链上,但其引用的其他对象可能尚未被标记。灰色对象不会直接体现在位图中,而是被放入一个专门的队列中,等待后续处理。 白色:表示对象不在GC Root的引用链上,因此可以被视为可回收的候选对象。在位图实现中,白色对象通过相应的bit位被标识为0。
2. 并发标记
在标记阶段开始时,G1垃圾收集器会创建一个当前所有对象的快照。 在这个快照之后新生成的对象,由于它们尚未被任何旧对象引用,因此它们会被直接标记为黑色,表示它们是活跃的,不应该被回收。 为了处理在标记过程中可能发生的对象引用变化,G1采用了 前置写屏障技术。 前置写屏障技术 会在引用赋值操作(如 B.c = null
)之前被触发,将即将被改变引用的对象(在这个例子中是C
)放入SATB待处理队列中。每个线程都有自己的SATB队列,但最终这些队列会被汇总到一个全局的SATB队列中。
3. 最终标记(Final Mark)
深度问题:SATB “浮动垃圾” 怎么来的?
并发标记阶段的并发性:
垃圾回收器(例如 G1、CMS)为了减少暂停时间,通常在回收过程中让应用程序的线程继续运行。这个阶段被称为并发标记阶段,回收器通过 SATB 算法来标记堆中的存活对象。 在此阶段,垃圾回收器开始标记那些通过根对象(GC Roots)可达的对象。SATB 通过在标记阶段开始时捕捉堆的快照来记录对象的引用关系,并据此标记对象。
SATB 使用 开始时的快照(这个是一个静态的快照),即在标记阶段开始时,存活对象已经在快照里边被标记。 在并发标记期间,应用程序继续运行,存活对象 可能已经被 改变了引用关系。例如,一个对象在标记阶段开始时是存活的,但在标记结束之前,已经没有应用关系,已经死了。但是SATB 并不知道。
在标记阶段开始时,如果一个对象是存活的(即它被引用),但在标记过程中应用程序修改了引用关系,使得该对象变得不再被引用,理论上该对象在本轮标记结束时已经是垃圾。 然而,由于 SATB 是基于标记开始时的快照,在标记结束前,标记器不会察觉这个对象已经变成垃圾,因此它仍然被认为是存活的,无法在当前回收周期内被回收。 这些在标记过程中变成垃圾但未能及时被标记器识别的对象就形成了浮动垃圾。它们不会被当前的回收周期回收,而是留到下一个垃圾回收周期才会被处理。
浮动垃圾是并发回收的自然现象,主要源于垃圾回收器在标记阶段与应用程序线程的并发执行。在这种并发执行的环境中,程序的状态会不断变化,而垃圾回收器基于“快照”进行标记,因此只能根据标记开始时的状态来做判断,无法实时跟踪对象的存活状态变化。 SATB 的快照机制虽然减少了停顿时间和性能开销,但由于它是基于标记开始时的状态,如果在标记阶段结束前对象的引用发生变化(如对象被废弃),这些对象就会变成“浮动垃圾”。
影响: 浮动垃圾无法在当前 GC 周期内被回收,它们会暂时继续占用内存。这会导致短期内内存占用的增加,甚至可能导致内存膨胀。 如果浮动垃圾过多,可能会增加下一次 GC 的负担,并可能在系统的极端高并发下引发内存压力。 应对方法:
增加堆内存:如果浮动垃圾引起内存膨胀,可以增加堆内存来缓解短期内的内存压力。 优化 GC 配置:通过调节垃圾回收器的参数,如 G1 的暂停时间目标,或者调节老年代与年轻代的比例,优化内存的回收频率,减少浮动垃圾的积累。 调整应用代码:有时候应用程序的对象生命周期管理可以优化,减少在并发标记期间频繁创建和丢弃对象的情况,降低浮动垃圾的数量。
4.清理(Cleanup)
5.转移
区域选择: 根据最终标记的结果,垃圾收集器会分析每个内存区域中垃圾对象所占用的内存大小。 在此基础上,结合预期的停顿时间,垃圾收集器会选择转移效率最高的若干个区域进行转移操作。 选择的标准通常是基于垃圾对象数量和区域的整体活跃对象比例,以最大化单次转移过程中的清理效率。
对象转移: 在选择好目标区域后,垃圾收集器会开始转移过程。 转移时,首先会处理GC Root直接引用的对象,这些对象通常是垃圾回收过程中的根节点,它们保证了程序的运行不会因垃圾回收而中断。 在复制这些对象之后,垃圾收集器会继续转移其他非直接引用的对象,直到所有选定区域中的存活对象都被复制到新的内存区域。
引用关系更新: 在对象转移完成后,垃圾收集器会清理掉原先区域中的垃圾对象,释放相应的内存空间。 如果外部的其他区域对象引用了已经被转移的对象,垃圾收集器还需要更新这些引用关系,确保它们指向新的内存位置。 这一步骤是确保程序在垃圾回收后能够继续正确运行的关键。
11.7. G1的Full GC
触发条件: Full GC通常在以下情况下被触发:
老年代的占用率达到了一定阈值,无法为新晋升的对象提供足够的空间。 无法找到足够的连续空间来存放大对象(Humongous Objects)。 在并发标记阶段,老年代被填满,无法等待并发标记完成。 显式调用 System.gc()
,尽管G1会尽量忽略这个请求,但某些情况下仍然可能触发。
G1的Full GC会暂停所有应用线程(Stop-The-World)。 进行全堆的标记、清理和压缩整理。 Full GC这个过程可能会使用Serial Old收集器,它是单线程的,会进一步增加GC的停顿时间。 "Serial Old" 垃圾回收(GC)指的是 Java 虚拟机(JVM)中的一种垃圾回收器,属于 老年代(Old Generation) 的垃圾回收机制。它是基于 标记-整理(Mark-Compact) 算法的单线程回收器,通常与 Serial GC 搭配使用,形成一种简单、低开销但不适合高并发环境的垃圾回收策略。 Full GC会尝试回收所有可回收的对象,包括年轻代和老年代。
避免Full GC的关键是保持G1的Mixed GC和Young GC的有效运行,确保它们能够及时回收内存。 监控和分析GC日志,识别触发Full GC的原因,并进行相应的优化,如增加堆大小、调整G1的参数等。 避免在老年代中分配过大的对象,或者确保有足够的连续空间来存放这些大对象。
G1在执行Full GC时,会在GC日志中记录相关信息,如 [Full GC (Allocation Failure)]
,表示由于分配失败触发了Full GC。
Full GC是一个昂贵的操作,因为它涉及到整个堆的回收,可能会导致显著的停顿时间,影响应用程序的性能。
通过合理配置G1的参数,如 -XX:InitiatingHeapOccupancyPercent
、-XX:G1HeapWastePercent
和-XX:G1MixedGCCountTarget
,可以调整G1的行为,以减少Full GC的发生。
暂停时间的控制
年轻代大小的配置
11.8. CMS与G1的区别
区别 | CMS | G1 |
2 并发标记 3 重新标记(STW) 4 并发清除 | 2 并发标记 3 最终标记(STW) 4 筛选回收(STW) | |
2 空间整理:不会产生空间碎片 | ||
2 无法处理浮动垃圾,内存不足时出现“Concurrent Mode Failure”(并发模式故障),切换到SerialOld收集模式 3 CPU敏感资源敏感,第二阶段并发阶段虽然不会导致用户线程停顿,但如果再CPU资源不足情况下,应用会有明显卡顿 | ||
2.JDK7及更低版本同等环境下 可选择CMS (G1不完善 | 2.实时数据占用超过一半的堆空间 3.对象分配或者晋升的速度变化大 4.希望消除长时间的GC停顿 |
11.9. G1的 配置参考
规格实例 | 配置参数 |
11.10. G1的GC垃圾回收日志解读
年轻代GC日志(完全年轻代)
//[GC pause (G1 Evacuation Pause) (young) 代表完全年轻代回收
// 0.0182341 secs 是本次GC的暂停时间
0.184: [GC pause (G1 Evacuation Pause) (young), 0.0182341 secs 是本次GC的暂停时间]
// 并行GC线程,一共有8个
[Parallel Time: 16.7 ms, GC Workers: 8]
/*这一行信息说明的是这8个线程开始的时间,Min表示最早开始的线程时间,Avg表示平均开始时间,Max表示的是最晚开始时间,Diff为最早和最晚的时间差。这个值越大说明线程启动时间越不均衡。线程启动的时间依赖于GC进入安全点的情况。关于安全点可以参考后文的介绍。*/
[GC Worker Start (ms): 184.2 184.2 184.2 184.3 184.3 184.4 186.1 186.1
Min: 184.2, Avg: 184.7, Max: 186.1, Diff: 1.9]
/*根处理的时间,这个时间包含了所有强根的时间,分为Java根,分别为Thread、JNI、CLDG;和JVM根下面的StringTable、Universe、JNI Handles、ObjectSynchronizer、FlatProfiler、Management、SystemDictionary、JVMTI */
[Ext Root Scanning (ms): 0.3 0.2 0.2 0.1 0.1 0.0 0.0 0.0
Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 0.8]
/*Java线程处理时间,主要是线程栈。这个时间包含了根直接引用对象的复制时间,如果根超级大,这个时间可能会增加 */
[Thread Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[StringTable Roots (ms): 0.0 0.1 0.1 0.1 0.1 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.4]
[Universe Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[JNI Handles Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[ObjectSynchronizer Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[FlatProfiler Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Management Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[SystemDictionary Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[CLDG Roots (ms): 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.3, Diff: 0.3, Sum: 0.3]
[JVMTI Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
// CodeCache Roots实际上是在处理Rset的时候的统计值,它包含下面的
// UpdateRS,ScanRS和Code Root Scanning
[CodeCache Roots (ms): 5.0 3.9 2.2 3.3 2.1 2.2 0.6 2.2
Min: 0.6, Avg: 2.7, Max: 5.0, Diff: 4.4, Sum: 21.6]
[CM RefProcessor Roots (ms): 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Wait For Strong CLD (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Weak CLD Roots (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[SATB Filtering (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
// 这个就是GC线程更新RSet的时间花费,注意这里的时间和我们在Refine里面处理RSet
// 的时间没有关系,因为它们是不同的线程处理
[Update RS (ms): 5.0 3.9 2.2 3.3 2.1 2.2 0.6 2.2
Min: 0.6, Avg: 2.7, Max: 5.0, Diff: 4.4, Sum: 21.5]
// 这里就是GC线程处理的白区中的dcq个数
[Processed Buffers: 8 8 7 8 8 7 2 4
Min: 2, Avg: 6.5, Max: 8, Diff: 6, Sum: 52]
// 扫描RSet找到被引用的对象
[Scan RS (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): 0.0 0.0 0.0 0.0 0.0 0.1 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
// 这个就是所有活着的对象(除了强根直接引用的对象,在Java根处理时会直接复制)复制
// 到新的分区花费的时间。从这里也可以看出复制基本上是最花费时间的操作。
[Object Copy (ms): 11.3 12.5 14.2 13.1 14.3 14.2 14.2 12.5
Min: 11.3, Avg: 13.3, Max: 14.3, Diff: 3.0, Sum: 106.3]
// GC线程结束的时间信息。
[Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination Attempts: 1 1 1 1 1 1 1 1
Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
// 这个是并行处理时其他处理所花费的时间,通常是由于JVM析构释放资源等
[GC Worker Other (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
// 并行GC花费的总体时间
[GC Worker Total (ms): 16.6 16.6 16.6 16.5 16.5 16.4 14.7 14.7
Min: 14.7, Avg: 16.1, Max: 16.6, Diff: 1.9, Sum: 128.7]
// GC线程结束的时间信息
[GC Worker End (ms): 200.8 200.8 200.8 200.8 200.8 200.8 200.8 200.8
Min: 200.8, Avg: 200.8, Max: 200.8, Diff: 0.0]
// 下面是其他任务部分。
// 代码扫描属于并行执行部分,包含了代码的调整和回收时间
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
// 清除卡表的时间
[Clear CT: 0.1 ms]
[Other: 1.5 ms]
// 选择CSet的时间,YGC通常是0
[Choose CSet: 0.0 ms]
// 引用处理的时间,这个时间是发现哪些引用对象可以清除,这个是可以并行处理的
[Ref Proc: 1.1 ms]
// 引用重新激活
[Ref Enq: 0.2 ms]
// 重构RSet花费的时间
[Redirty Cards: 0.1 ms]
[Parallel Redirty: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Redirtied Cards: 8118 7583 6892 4496 0 0 0 0
Min: 0, Avg: 3386.1, Max: 8118, Diff: 8118, Sum: 27089]
// 这个信息是是可以并行处理的,这里是线程重构RSet的数目
// 大对象处理时间
[Humongous Register: 0.0 ms]
[Humongous Total: 2]
// 这里说明有2个大对象
[Humongous Candidate: 0]
// 可回收的大对象0个
// 如果有大对象要回收,回收花费的时间,回收的个数
[Humongous Reclaim: 0.0 ms]
[Humongous Reclaimed: 0]
// 释放CSet中的分区花费的时间,有新生代的信息和老生代的信息。
[Free CSet: 0.0 ms]
[Young Free CSet: 0.0 ms]
[Non-Young Free CSet: 0.0 ms]
// GC结束后Eden从15M变成0,下一次使用的空间为21M,S从2M变成3M,整个堆从
// 23.7M变成20M
[Eden: 15.0M(15.0M)->0.0B(21.0M) Survivors: 2048.0K->3072.0K
Heap: 23.7M(256.0M)->20.0M(256.0M)]
老年代垃圾回收(部分年轻代/混合回收)日志
并发标记日志
//并发标记 - 初始标记阶段,在年轻代GC中完成
100.070: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0751469 secs]
[Parallel Time: 74.7 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 100070.4, Avg: 100070.5, Max: 100070.6, Diff:
0.1]
[Ext Root Scanning (ms): Min: 0.1, Avg: 0.2, Max: 0.3, Diff: 0.2, Sum:
1.6]
[Update RS (ms): Min: 0.6, Avg: 1.1, Max: 1.5, Diff: 0.9, Sum: 8.9]
[Processed Buffers: Min: 1, Avg: 1.6, Max: 4, Diff: 3, Sum: 13]
[Scan RS (ms): Min: 1.0, Avg: 1.4, Max: 1.9, Diff: 0.9, Sum: 10.8]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum:
0.0]
[Object Copy (ms): Min: 71.5, Avg: 71.5, Max: 71.6, Diff: 0.1, Sum: 572.1]
[Termination (ms): Min: 0.3, Avg: 0.3, Max: 0.4, Diff: 0.1, Sum: 2.6]
[Termination Attempts: Min: 1382, Avg: 1515.5, Max: 1609, Diff: 227,
Sum: 12124]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 74.5, Avg: 74.5, Max: 74.6, Diff: 0.1, Sum:
596.3]
[GC Worker End (ms): Min: 100145.1, Avg: 100145.1, Max: 100145.1, Diff:
0.0]
[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.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: 23.0M(23.0M)->0.0B(14.0M) Survivors: 4096.0K->4096.0K Heap: 84.5M
(128.0M)->86.5M(128.0M)]
[Times: user=0.63 sys=0.00, real=0.08 secs]
// 把YHR中Survivor分区作为根,开始并发标记根扫描
100.146: [GC concurrent-root-region-scan-start]
// 并发标记根扫描结束,花费了0.0196297,注意扫描和Mutator是并发进行,同时有多个线程并行
100.165: [GC concurrent-root-region-scan-end, 0.0196297 secs]
// 开始并发标记子阶段,这里从所有的根引用:包括Survivor和强根如栈等出发,对整个堆进行标记
100.165: [GC concurrent-mark-start]
// 标记结束,花费0.08848s
100.254: [GC concurrent-mark-end, 0.0884800 secs]
// 这里是再标记子阶段,包括再标记、引用处理、类卸载处理信息
100.254: [GC remark 100.254: [Finalize Marking, 0.0002228 secs] 100.254:
[GC ref-proc, 0.0001515 secs] 100.254: [Unloading, 0.0004694 secs],
0.0011610 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
// 清除处理,这里的清除仅仅回收整个分区中的垃圾
// 这里还会调整RSet,以减轻后续GC中RSet根的处理时间
100.255: [GC cleanup 86M->86M(128M), 0.0005376 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
混合回收日志
// 混合回收Mixed GC其实和YGC的日志类似,能看到GC pause(G1EvacuationPause)(mixed)这样的信息
// 日志分析参考Y年轻代GC。
122.132: [GC pause (G1 Evacuation Pause) (mixed), 0.0106092 secs]
[Parallel Time: 9.8 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 122131.9, Avg: 122132.0, Max: 122132.0,
Diff: 0.1]
[Ext Root Scanning (ms): Min: 0.1, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.7]
[Update RS (ms): Min: 0.5, Avg: 0.7, Max: 0.9, Diff: 0.4, Sum: 5.4]
[Processed Buffers: Min: 1, Avg: 1.8, Max: 3, Diff: 2, Sum: 14]
[Scan RS (ms): Min: 1.0, Avg: 1.3, Max: 1.5, Diff: 0.5, Sum: 10.4]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum:
0.0]
[Object Copy (ms): Min: 7.5, Avg: 7.6, Max: 7.7, Diff: 0.2, Sum: 60.9]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Termination Attempts: Min: 92, Avg: 105.1, Max: 121, Diff: 29, Sum: 841]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 9.7, Avg: 9.7, Max: 9.8, Diff: 0.1, Sum: 77.6]
[GC Worker End (ms): Min: 122141.7, Avg: 122141.7, Max: 122141.7, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.2 ms]
[Other: 0.7 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.5 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 3072.0K(3072.0K)->0.0B(5120.0K) Survivors: 3072.0K->1024.0K
Heap: 105.5M(128.0M)->104.0M(128.0M)]
[Times: user=0.00 sys=0.00, real=0.01 secs]
11.11. G1 常用参数
-XX:+UseG1GC
开启# 启动G1
-XX:+UseG1GC
# 最小堆内存
-Xms8G
# 最大堆内存
-Xmx8G
# metaspace初始值
-XX:MetaspaceSize=256M
# 期望的最大暂停时间,默认200ms
-XX:MaxGCPauseMillis
# 简称为IHOP,默认值为45,这个值是启动并发标记的阈值,当老年代使用内存占用堆内存的45%启动并发标记。
# 如果该过大,可能会导致mixed gc跟不上内存分配的速度从而导致full gc
-XX:InitiatingHeapOccupancyPercent
# G1自动调整IHOP的指,JDK9之后可用
-XX:+G1UseAdaptiveIHOP
# 并发标记时可以卸载Class,这个操作比较耗时,对Perm/MetaSpace进行清理,默认未开启
-XX:+ClassUnloadingWithConcurrentMark
# 多个线程并行执行java.lang.Ref.*,对象回收前的引用处理
-XX:-ParallelRefProcEnabled
1. 堆内存配置 相关参数
配置 | 说明 | 建议 |
Heap Size | Region Size |
2. GC配置 相关参数
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:InitiatingHeapOccupancyPercent=50 -XX:MaxTenuringThreshold=10
配置 | 说明 | 建议 |
3. NMT 本地内存相关参数
-XX:NativeMemoryTracking=detail
配置 | 说明 | 建议 |
4. GC日志配置 相关参数
// Java8: -XX:+PrintGCCause -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution -XX:+PrintReferenceGC -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=2M // Java11以上: -Xlog:gc*=info,phases*=debug,region*=debug,age*=trace,ergo*=debug,safepoint,heap*=debug:file=gc.log:time,level,tags:filecount=5,filesize=2m
配置 | 说明 | 建议 |
5. 其他 参数
配置 | 说明 | 建议 |
11.2. G1 调优实战
11.3. 结语
说在最后:有问题找老架构取经
被裁之后, 空窗1年/空窗2年, 如何 起死回生 ?
案例1:42岁被裁2年,天快塌了,急救1个月,拿到开发经理offer,起死回生
案例2:35岁被裁6个月, 职业绝望,转架构急救上岸,DDD和3高项目太重要了
案例3:失业15个月,学习40天拿offer, 绝境翻盘,如何实现?
被裁之后,100W 年薪 到手, 如何 人生逆袭?
100W案例,100W年薪的底层逻辑是什么? 如何实现年薪百万? 如何远离 中年危机?
如何 逆天改命,包含AI、大数据、golang、Java 等
实现职业转型,极速上岸
关注职业救助站公众号,获取每天职业干货
助您实现职业转型、职业升级、极速上岸
---------------------------------
实现架构转型,再无中年危机
关注技术自由圈公众号,获取每天技术千货
一起成为牛逼的未来超级架构师
几十篇架构笔记、5000页面试宝典、20个技术圣经
请加尼恩个人微信 免费拿走
暗号,请在 公众号后台 发送消息:领电子书
如有收获,请点击底部的"在看"和"赞",谢谢