作者:微信小助手
发布时间:2025-03-04T11:58:13
正文开始...... Region 分区模型 G1 将堆内存划分为多个等大小的 Region(默认 1MB-32MB),每个 Region 可以是 Eden、Survivor 或 Old 区,默认是将堆内存按照 2048 份均分。 这种灵活的分区方式打破了传统代际的物理隔离,允许动态调整各代内存占比。 Region 的角色(年轻代、老年代或大对象区)是动态分配的。例如,一个 Region 可能初始作为 Eden 区,回收后被标记为 Survivor 区,后续可能转为老年代区。 大对象(Humongous 对象):若对象大小超过 Region 的 50%,则分配到连续的 Humongous Region。此类对象回收需特殊处理,若空间不足可能触发 Full GC。 例如,当老年代占用过高时,G1 会优先回收垃圾最多的 Region,而非全堆扫描。 跨代引用的智能追踪 卡表(Card Table):记录跨 Region 的引用关系。例如,老年代对象引用年轻代对象时,对应的卡表条目会被标记为“脏卡”。 记忆集(RSet):每个 Region 维护一个 RSet,存储其他 Region 对其内部对象的引用。通过 RSet 快速定位跨 Region 引用,避免全堆扫描。 写屏障(Write Barrier):在对象引用修改时触发,更新卡表和 RSet。例如,当老年代对象引用新生代对象时,写屏障会记录该引用。 混合回收(Mixed GC) 混合回收是 G1 的精髓:在一次回收中,同时处理年轻代和老年代的 Region。 通过计算回收收益(垃圾量/耗时),G1 选择性价比最高的 Region 集合(Collection Set),在用户设定的最大停顿时间内(如 200ms)完成回收。 在逻辑上,G1 分为年轻代和老年代,但它的年轻代和老年代比例,并不是那么“固定”,为了达到 MaxGCPauseMillis 所规定的效果,G1 会自动调整两者之间的比例。 如果你强行使用 -Xmn 或者 -XX:NewRatio 去设定它们的比例的话,我们给 G1 设定的这个目标将会失效。 G1 的回收过程主要分为 3 类: Chaya:年轻代回收触发流程是什么? Eden 区占满时触发,仅回收年轻代 Region。回收流程如下所示: 根扫描:标记 GC Roots 直接可达的对象(如栈帧局部变量、静态变量)。 RSet 处理:通过脏卡队列更新 RSet,将老年代对年轻代的引用加入 GC Roots。 复制存活对象:将 Eden 和 Survivor 区的存活对象复制到新 Survivor 区,年龄达阈值(默认 15)则晋升老年代。 这个过程通常是 Stop-The-World(STW)的,即在回收过程中,应用程序的其他线程会被暂停。 Chaya:混合回收的触发条件是什么? 多次回收之后,会出现很多 Old 老年代区,此时总堆占有率达到阈值(默认 45%)时会触发混合回收 MixedGC。混合回收会回收 整个年轻代 + 部分老年代。 回收过程如下: 注意:当混合回收无法快速释放足够空间时触发 Full GC(如大对象分配失败),采用单线程标记-整理算法,导致长停顿。 初始标记会暂停所有用户线程,只标记从 GC Root 可直达的对象,所以停顿时间不会太长。 采用三色标记法进行标记,三色标记法在原有双色标记(黑也就是 1 代表存活,白 0 代表可回收)增加了一种灰色。 三色标记法 默认线程数为 允许系统程序的运行,同时进行"GC Roots"追踪,追踪所有存活对象(间接引用的对象)。该阶段很耗时,因为要追踪全部的存活对象。但是是并发运行,对系统影响不大。 GC 开始前所有对象都是白色,GC 一开始所有根能够直达的对象被压到栈中,待搜索,此时颜色是灰色。 然后灰色对象依次从栈中取出搜索子对象,子对象也会被涂为灰色,入栈。 当其所有的子对象都涂为灰色之后该对象被涂为黑色。 当 GC 结束之后灰色对象将全部没了,剩下黑色的为存活对象,白色的为垃圾。 需要注意的是:由于用户线程可能同时在修改对象的引用关系,就会出现错标的情况 Chaya:那咋办呢? G1 为了解决这个问题,使用了SATB 技术(Snapshot At The Beginning, 初始快照)。 在并发标记开始时,G1 会创建一个堆内存的快照,记录所有存活对象的初始状态。 在最终标记阶段,系统会处理并发期间新增的引用变化,通过写前屏障(Write Barrier)记录这些变化,确保新对象不被错误回收。 触发时机:在并发标记(Concurrent Marking)完成后,G1 需要暂停所有应用线程(STW),以处理并发标记期间遗漏的引用变化,确保标记结果的准确性。 核心目标:修正并发标记阶段因应用线程并发执行导致的对象引用变化(如新对象创建或引用更新),并生成最终的存活对象快照。 处理漏标对象:通过遍历卡表(Card Table)中的“脏页”(记录引用修改的区域),重新扫描这些区域的对象,修正标记状态 最终标记完成后,G1 根据停顿时间目标(MaxGCPauseMillis)和Region 回收价值,选择最合适的区域进行回收。 优先回收垃圾比例高(存活对象少)的 Region,以最小化回收时间并最大化内存释放效率。 根据每个 Region 的存活对象数量和回收时间成本计算“回收价值”,优先选择存活率低、回收效率高的 Region 组成回收集(Collection Set, CSet)。 标记-复制算法:将存活对象从回收集的 Region 复制到空闲 Region,同时整理内存以减少碎片。 主要步骤如下所示: 案例:某线上服务因误设 解决方案:删除 动态年龄判定:若 Survivor 区使用超过 50%( 混合回收触发阈值:默认 大对象(超过 Region 50%)直接进入老年代,类似超重订单需特殊车辆处理。通过 内存不足:堆内存过小或老年代晋升过快,需检查 并发失败:若 Mixed GC 无法及时回收,触发 Full GC,需优化 启用 GC 日志: 关键调优参数 G1 是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的 GC 暂停目标,就能得到不错的性能;同时,我们也看到 G1 对内存空间的浪费较高,但通过首先收集尽可能多的垃圾(Garbage First)的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。 虽然 G1 也有类似 CMS 的收集动作:初始标记、并发标记、重新标记、清除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种的过程描述显得并不是很妥当。
G1 分区模型
年轻代回收(Young GC)
混合回收(Mixed GC)
初始标记
并发标记
ParallelGCThreads
的 1/4(通过-XX:ConcGCThreads
调整),减少应用线程阻塞。
最终标记
筛选回收
-XX:MaxGCPauseMillis
),动态选择需要回收的 Region。
调优策略与参数
案例一:年轻代配置
-Xmn256m
覆盖 G1 的自动调节,导致 Eden 区过小(仅 256MB),频繁触发 Young GC(600+次/压测),响应时间激增。-Xmn
参数,由 G1 根据G1NewSizePercent
(默认 5%)和G1MaxNewSizePercent
(默认 60%)动态调整新生代大小,GC 时间从 25 秒降至 1 秒内。案例二:老年代“拥堵治理”
TargetSurvivorRatio
默认值),对象会直接晋升老年代。需通过增大 Survivor 区或降低晋升阈值(MaxTenuringThreshold
),避免过早“占道”。InitiatingHeapOccupancyPercent=45%
(老年代占比),高并发场景可适度调低以提前回收,避免 Full GC。大对象“专车配送”
G1HeapRegionSize
调整 Region 大小(如 32MB),或设置PretenureSizeThreshold
控制大对象阈值,减少内存碎片。Full GC 的应急处理
-Xmx/-Xms
是否一致(建议设为物理内存 75%-80%)。MaxGCPauseMillis
或降低InitiatingHeapOccupancyPercent
。-XX:+PrintGCDetails -Xloggc:/path/gc.log
,关注Full GC
关键字及耗时。
-XX:MaxGCPauseMillis
:设定最大停顿时间(默认 200ms),G1 根据此目标动态调整回收 Region 数量.
-XX:G1HeapRegionSize
:手动指定 Region 大小(需为 2 的幂次方)。
-XX:G1MixedGCCountTarget
:控制混合回收次数(默认 8 次),分批次回收老年代 Region 以减少单次停顿。
-XX:G1ReservePercent
:预留堆内存(默认 10%)防止晋升失败。
总结