遇见pod重启

遇见系列 第五篇

现场的应用A pod一段时间就会重启,用k8s命令看pod是因为超出limits。

查看了gc日志。因为使用的是G1回收器

让我详细解释G1 GC的工作流程:

内存分区:

整个堆内存被划分为大小相等的Region(默认2048个)
Region大小是2的幂次方,范围从1MB到32MB
每个Region可以是Eden、Survivor、Old或Humongous区
Humongous区专门用于存储大于Region大小一半的对象

GC周期的四个主要阶段:

a) 初始标记(Initial Mark)- STW:

标记所有直接从GC Roots可达的对象
这个阶段需要Stop-The-World(STW)
通常和年轻代收集同时进行
时间很短

b) 并发标记(Concurrent Mark):

遍历整个堆的对象图
标记所有可达的对象
与应用程序并发执行
使用SATB(Snapshot-At-The-Beginning)算法
记录标记期间的引用变化

c) 最终标记(Final Mark)- STW:

处理并发标记阶段遗留的SATB缓冲区
完成最终的标记工作
需要STW,但时间较短

d) 筛选回收(Live Data Counting and Evacuation)- STW:

计算各个Region的垃圾比例
根据用户期望的停顿时间建立回收集合
优先回收价值最大的Region(垃圾最多)
将存活对象复制到新的Region
清空旧Region

特殊处理:

a) 年轻代收集:

当Eden区满时触发
只关注年轻代Region
存活对象复制到Survivor区或老年代

b) 混合收集:

同时回收年轻代和部分老年代Region
根据全局并发标记的结果
老年代Region按垃圾收集价值排序

优化机制:

a) Remembered Sets(Rsets):

记录跨Region的引用关系
每个Region都有一个RSet
避免全堆扫描

b) Collection Sets(CSets):

需要被回收的Region集合
基于垃圾占比和期望停顿时间选择

正常GC流程:

Young GC:回收年轻代Region
并发标记周期:标记整个堆的存活对象
混合回收:回收年轻代和部分老年代Region

Full GC触发条件:

并发标记完成之前就出现内存分配失败
回收后的空间仍不足以支持下一次Young GC(空间担保失败)
混合回收后仍无法满足内存需求
Humongous对象分配失败

Full GC的特点:

必须Stop-The-World(STW)
对整个堆进行压缩和整理
单线程执行,停顿时间长
会明显影响应用程序性能

Full GC后的处理:

如果Full GC后内存足够,应用继续运行
如果Full GC后内存仍然不足,抛出OutOfMemoryError

避免Full GC的建议:

合理设置堆内存大小
调整并发标记的触发阈值
适当调整Region大小
控制大对象的产生
及时进行并发标记,避免内存耗尽

Full GC在G1中的具体执行步骤:

  1. 标记阶段(Marking Phase):
  • 进入STW阶段,停止所有用户线程
  • 从GC Roots开始进行全堆标记
  • 标记所有存活对象
  • 使用单线程进行标记,效率较低
  1. 清理阶段(Cleanup Phase):
  • 清理所有未被标记的对象(垃圾对象)
  • 回收空的Region
  • 重置各种元数据信息(RSet等)
  • 计算所有Region的存活对象信息
  1. 压缩阶段(Compaction Phase):
  • 对存活对象进行压缩整理
  • 将存活对象复制到新的Region
  • 按照新的分代布局重新整理对象
  • 更新对象引用关系
  • 释放旧的Region
  • 重建RSet等辅助数据结构

特点:

  • 整个过程完全STW
  • 单线程执行,性能较差
  • 会对整个堆内存进行处理
  • 会导致较长时间的停顿
  • 会破坏原有的分代结构,需要重新构建

触发Full GC通常意味着:

  • 内存分配压力过大
  • 并发收集来不及回收内存
  • 可能需要调整GC参数或应用程序代码