第2章 堆内存基础回顾:堆结构、代际划分、GC算法、常用参数
现象识别:堆内存异常的典型信号
堆内存配置不当或运行时失衡,会通过以下现象暴露问题。排查工程师需快速将这些信号与堆结构、GC行为关联。
| 现象 | 典型日志/指标 | 可能的堆层面原因 |
|---|---|---|
| 频繁 Full GC | GC 日志中 Full GC 间隔 < 5 秒 | 老年代过小 / 对象提前晋升 |
| OutOfMemoryError: Java heap space | 堆转储分析显示老年代或 Eden 占满 | 堆容量不足 / 内存泄漏 |
| GC overhead limit exceeded | 98% 时间用于 GC,回收 < 2% 堆 | 堆过小 / 对象生命周期异常 |
| 进程响应缓慢、CPU 飙升 | GC 线程占用高 / 用户线程停顿长 | GC 算法选择不当 / 大对象直接进入老年代 |
判断规则:若 Full GC 频率 > 1 次 / 分钟且每次回收后老年代占用率仍 > 80%,优先怀疑堆结构参数(代际比例)不合理,而非单纯容量不足。
数据采集:获取堆内存与 GC 配置
数据采集阶段应聚焦以下三类信息:
- 当前 JVM 参数:通过
-XX:+PrintFlagsFinal或启动命令确认堆大小、代际比例。典型命令如下:
# 查看 JVM 内存相关参数(示例)
java -XX:+PrintFlagsFinal -version | grep -E "HeapSize|NewSize|MaxNewSize|OldSize|MetaspaceSize"
- GC 日志:需开启详细日志以定位代际占用变化和时钟停顿。
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=10M
- 堆转储快照:在 OOM 或频繁 Full GC 时触发 dump,用于分析代际分布和泄漏对象。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof
原因定位:堆结构、代际划分与 GC 算法如何影响问题
3.1 堆内存标准结构
HotSpot 堆由年轻代(Young)、老年代(Old)和元空间(Metaspace,非堆)组成。年轻代又分为 Eden、Survivor0(S0)和 Survivor1(S1)。对象流转路径如下。
3.2 代际划分关键参数
| 参数 | 用途 | 默认值 (JDK 8+) |
|---|---|---|
-Xms / -Xmx | 堆初始/最大大小 | 物理内存 1/64 ~ 1/4 |
-Xmn 或 -XX:NewSize / MaxNewSize | 年轻代大小 | 堆的 1/3 |
-XX:SurvivorRatio | Eden : S0 = Eden : S1 | 8(即 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 日志,关注三个核心指标:
- Full GC 频率:是否从每分钟 >1 次降至 <0.1 次
- 单次 STW 停顿时间:G1 下应 < 200ms,Parallel 下 < 1s
- 堆占用曲线:老年代占用是否稳定在 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 检查晋升速率。