第2章 堆内存基础回顾:堆结构、代际划分、GC算法、常用参数

现象识别:堆内存异常的典型信号

堆内存配置不当或运行时失衡,会通过以下现象暴露问题。排查工程师需快速将这些信号与堆结构、GC行为关联。

现象典型日志/指标可能的堆层面原因
频繁 Full GCGC 日志中 Full GC 间隔 < 5 秒老年代过小 / 对象提前晋升
OutOfMemoryError: Java heap space堆转储分析显示老年代或 Eden 占满堆容量不足 / 内存泄漏
GC overhead limit exceeded98% 时间用于 GC,回收 < 2% 堆堆过小 / 对象生命周期异常
进程响应缓慢、CPU 飙升GC 线程占用高 / 用户线程停顿长GC 算法选择不当 / 大对象直接进入老年代
判断规则:若 Full GC 频率 > 1 次 / 分钟且每次回收后老年代占用率仍 > 80%,优先怀疑堆结构参数(代际比例)不合理,而非单纯容量不足。

数据采集:获取堆内存与 GC 配置

数据采集阶段应聚焦以下三类信息:

  1. 当前 JVM 参数:通过 -XX:+PrintFlagsFinal 或启动命令确认堆大小、代际比例。典型命令如下:
# 查看 JVM 内存相关参数(示例)
java -XX:+PrintFlagsFinal -version | grep -E "HeapSize|NewSize|MaxNewSize|OldSize|MetaspaceSize"
  1. GC 日志:需开启详细日志以定位代际占用变化和时钟停顿。
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=10M
  1. 堆转储快照:在 OOM 或频繁 Full GC 时触发 dump,用于分析代际分布和泄漏对象。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof

原因定位:堆结构、代际划分与 GC 算法如何影响问题

3.1 堆内存标准结构

HotSpot 堆由年轻代(Young)、老年代(Old)和元空间(Metaspace,非堆)组成。年轻代又分为 Eden、Survivor0(S0)和 Survivor1(S1)。对象流转路径如下。

堆内存代际划分与GC流程 堆代际结构与对象晋升流程 年轻代 (Young Gen) Eden 对象初次分配 S0 S1 老年代 (Old Gen) 生命周期较长的对象 晋升阈值:-XX:MaxTenuringThreshold=15 晋升 元空间 (Metaspace) 类元数据 / 静态常量池 Minor GC:在年轻代内回收 Major/Full GC:跨代回收(老年代+年轻代)

3.2 代际划分关键参数

参数用途默认值 (JDK 8+)
-Xms / -Xmx堆初始/最大大小物理内存 1/64 ~ 1/4
-Xmn 或 -XX:NewSize / MaxNewSize年轻代大小堆的 1/3
-XX:SurvivorRatioEden : S0 = Eden : S18(即 Eden:S0:S1 = 8:1:1)
-XX:MaxTenuringThreshold晋升老年代前经历 Minor GC 次数15(CMS 下可为 6)
-XX:MetaspaceSize / MaxMetaspaceSize元空间初始/最大21 MB / 无限制

3.3 常见 GC 算法及适用场景

GC 算法的选择直接影响堆代际回收效率。排查时需结合日志中的 GC 类型(Young / Full / Concurrent)判断。

算法回收区域特点常见配置
Serial GC年轻代 + 老年代单线程 Stop-the-world;适合客户端/小堆-XX:+UseSerialGC
Parallel Scavenge + Parallel Old年轻代 (ParScav) + 老年代 (ParOld)多线程回收,高吞吐;适合批处理/后台任务-XX:+UseParallelGC (自动启用 Parallel Old)
CMS老年代并发回收低停顿,但易产生碎片-XX:+UseConcMarkSweepGC
G1全堆(分 region)可预测停顿;适合大堆 (>4GB)-XX:+UseG1GC
排查判据:若垃圾收集日志中出现大量 "Promotion Failed" 或 "Concurrent Mode Failure",表明老年代过小或晋升过于集中;此时应检查 -Xmn 比例和 -XX:SurvivorRatio,并考虑增大老年代或切换至 G1。

修复动作:基于定位结果的参数调整

  • 调整代际比例:当年轻代频繁触发 Minor GC 且存活对象快速晋升(观察 GC 日志中晋升到老年代的大小),适当增加 -Xmn 或调整 -XX:SurvivorRatio 为 6 或 4,让 Survivor 空间容纳更多短期对象。
  • 增大堆容量:若老年代占用率持续 > 80% 且无泄漏,逐步增加 -Xmx 至物理内存的 70%~80%(预留系统/元空间)。
  • 切换 GC 算法:对于低延迟要求应用(如 Web 前端),推荐从 Parallel 切换至 G1,并设置 -XX:MaxGCPauseMillis=200 以控制停顿。
# 示例:为 8GB 物理内存的服务器调整堆参数(G1)
-Xms4g -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:NewRatio=3 -XX:SurvivorRatio=6 -XX:MetaspaceSize=256m

复测:验证调整效果

使用相同负载进行基准测试,采集调整前后的 GC 日志,关注三个核心指标:

  1. Full GC 频率:是否从每分钟 >1 次降至 <0.1 次
  2. 单次 STW 停顿时间:G1 下应 < 200ms,Parallel 下 < 1s
  3. 堆占用曲线:老年代占用是否稳定在 60%~75%
常见误区:增大 -Xmx 不等于降低 Full GC 频率。若未同步调整代际比例,更大堆可能导致 Minor GC 停顿时间线性增长。始终结合 GC 日志中的 "promotion" 和 "occupancy" 数据做精准调参。

预防:建立堆内存监控基线

  • 启动参数标准化:对所有微服务统一启用 GC 日志、OOM dump 及以下监控参数:
-Xlog:gc*:file=/var/log/jvm/gc.log:time,uptime,level:filecount=10,filesize=10M
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps/
  • 周期性健康检查:通过 JMX 或 Prometheus 采集 jvm.gc.collection.count、jvm.memory.used.percent,当日 Full GC 次数超过阈值(如 5 次/天)自动告警。
  • 发布前压测:每次版本变更执行 30 分钟稳定流量压测,观察堆代际占用是否符合预期,必要时触发 jmap -histo:live 模拟 Full GC 检查晋升速率。