Appearance
堆
堆是 JVM 管理对象实例和数组的主要区域,也是 GC 最核心的工作区。大多数 Java 内存问题最终都会回到两个问题:对象为什么还活着,堆为什么释放不下来。
概念边界
| 项目 | 说明 |
|---|---|
| 线程关系 | 线程共享 |
| 存放内容 | 对象实例、数组 |
| 主要参数 | -Xms、-Xmx |
| 常见异常 | OutOfMemoryError: Java heap space、GC overhead limit exceeded |
| 主要工具 | heap dump、GC 日志、jmap、jcmd、MAT、JProfiler |
现代 HotSpot 的堆结构由具体 GC 决定。传统分代理解仍然有用:新对象通常先进入年轻代,存活时间较长的对象进入老年代;G1、ZGC 等回收器内部实现不同,但仍围绕对象生命周期和回收收益做取舍。
对象生命周期
常见路径:
text
创建对象 -> 进入堆 -> 被引用链持有 -> 参与 GC 存活判断 -> 回收或继续存活对象无法被回收,不是因为它“大”,而是因为它仍然可达。泄漏排查重点是引用链:
text
GC Roots -> 静态集合 / 缓存 / ThreadLocal / 监听器 -> 大对象常见问题
Java heap space
现象:
text
java.lang.OutOfMemoryError: Java heap space排查步骤:
- 查看
-Xmx是否过小。 - 抓取 heap dump。
- 找大对象、对象数量异常和引用链。
- 判断是业务容量不足、缓存无界、集合泄漏,还是短时间流量峰值。
保留现场:
bash
jcmd <pid> GC.heap_dump /tmp/app.hprof
jcmd <pid> GC.class_histogram > /tmp/histo.txtFull GC 频繁
常见原因:
- 老年代增长过快。
- 大对象直接进入老年代。
- 缓存、静态集合或 ThreadLocal 持有对象。
- 堆太小或对象存活率过高。
排查:
bash
jstat -gcutil <pid> 1000
jcmd <pid> GC.heap_info结合 GC 日志看回收后内存是否明显下降。如果每次 Full GC 后占用仍然很高,更像泄漏或容量不足。
GC overhead limit exceeded
含义:JVM 花大量时间 GC,但回收效果很差。
处理:
- 不要只增大堆,先确认对象为什么释放不掉。
- 抓 dump 看引用链。
- 检查批量加载、分页、缓存、队列积压。
参数
bash
-Xms2g
-Xmx2g生产服务常把 -Xms 和 -Xmx 设置为相同值,减少运行中扩缩堆带来的抖动。容器环境还要给直接内存、线程栈、元空间、Code Cache 留空间。
观测指标
| 指标 | 判断 |
|---|---|
| 堆使用率持续升高 | 可能存在泄漏、缓存增长或负载上升 |
| Full GC 后不下降 | 重点查存活对象引用链 |
| Young GC 很频繁 | 分配速率高或年轻代太小 |
| 老年代快速增长 | 晋升过快、大对象、存活率高 |
处理建议
- 先用 dump 和日志确认问题类型,再调参数。
- 缓存必须有上限、过期策略和监控。
- 批处理要控制单批数据量,避免一次性加载过多对象。
- 大对象、图片、文件内容不应长期留在堆中。
- 使用对象池要谨慎,错误的池化可能让对象更难回收。
