直接看内存使用结构比猜更准,需运行jmap -histo:live 查存活对象排名,重点关注[b等数组类、自定义业务类实例突增、threadlocal关联大对象;再用jstat -gcutil 1000 5观察gc行为,结合jcmd vm.native_memory及/proc//fd核对非堆资源,并通过两次堆转储对比dominator tree定位增量泄漏点。

直接看内存使用结构,比猜更准。资源浪费往往藏在“用了但没释放”或“分配了但根本用不上”的地方,而不是单纯看总内存是否吃紧。
查堆内对象分布,找高频未回收实例
运行 jmap -histo:live
- [B、[C、[I 等数组类——比如 [B(byte[])占用异常高,常对应缓存未清理、日志/文件流未关闭、Base64解码后未释放字节数组
- 自定义业务类(如 OrderService、CacheEntry)实例数突增且长期不降——说明对象被意外强引用,GC无法回收
- 线程本地变量(ThreadLocal)关联的 Map 或大对象——ThreadLocal 若未 remove(),会导致整个 value 链路内存泄漏
看 GC 行为,识别隐性资源滞留
执行 jstat -gcutil
- 如果 YGC 频繁但 FGC 几乎不触发,而老年代使用率缓慢上升 → 可能是中等生命周期对象堆积(如定时任务生成的中间结果未清理)
- 如果 FGC 频次高(如每秒一次)、每次回收量小 → 老年代碎片化或存在“假存活”对象(如监听器注册后未反注册)
- Survivor 区长期接近满、对象频繁晋升 → 新生代过小或对象“活得太久”,本质是短生命周期对象被意外延长
核对非堆内存与系统资源配额
堆外资源浪费容易被忽略,但同样导致 OOM 或性能抖动:
- 用 jcmd
VM.native_memory summary scale=MB 查 DirectMemory、CodeCache、Metaspace 占用 - Metaspace 持续增长 → 动态生成类过多(如频繁使用 CGLIB、JSON 反序列化框架未设缓存、热部署未清理旧类加载器)
- DirectMemory 接近上限 → NIO ByteBuffer.allocateDirect() 未显式 clean(),或 Netty Channel 未及时释放
- 结合 ls -l /proc/
/fd | wc -l 和 pstree -p| wc -l 确认文件句柄、线程数是否逼近系统 limit
对比 dump 前后快照,定位增量泄漏点
在业务低峰期分别执行两次堆转储(jmap -dump:format=b,file=heap1.hprof
- 关注 “Retained Heap” 增长显著的类——不是实例数多,而是它持有的子对象链路总内存变大
- 检查 Path to GC Roots 中是否存在 Thread、static 字段、ClassLoader 等强引用路径
- 特别留意第三方 SDK 内部缓存(如 OkHttp 的 ConnectionPool、Jackson 的 ObjectMapper 缓存)是否全局单例且无淘汰策略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











