Java内存泄漏早期排查流程(未触发频繁Full GC前)

 更新时间:2026年06月05日 09:39:11   作者:码不停蹄的玄黓  
内存泄漏早期最典型特征:老年代/堆内存呈缓慢、持续、不可逆的上涨,但还没严重到频繁Full GC、OOM,本文给大家介绍了一套生产环境可直接落地的排查流程,从简单到深入,不影响业务,需要的朋友可以参考下

内存泄漏早期最典型特征老年代/堆内存呈缓慢、持续、不可逆的上涨,但还没严重到频繁Full GC、OOM。
这个阶段最容易定位根因,核心思路是:监控趋势 → 定位泄漏区域 → 抓取堆快照 → 分析泄漏对象 → 定位代码

下面给你一套生产环境可直接落地的排查流程,从简单到深入,不影响业务。

一、第一步:先确认「真的是内存泄漏」

先排除正常内存使用、堆设置过小等情况。

1. 看内存增长曲线(最关键)

通过监控看 堆内存/老年代 趋势:

  • 正常:GC 后内存会大幅回落,曲线呈锯齿状
  • 泄漏:每次GC后回落很少,整体持续缓慢上涨

2. 快速命令验证(无需工具)

jstat -gcutil <pid> 1000 10  # 每1秒输出一次GC情况,共10次

重点看 3 个指标:

  • O:老年代使用率
  • FGC:Full GC 次数
  • FGCT:Full GC 总耗时

泄漏早期特征

  • O 持续缓慢上升(比如从 30% → 40% → 50%)
  • FGC 很少(0~几次)
  • 每次Full GC后,老年代下降幅度极小

只要满足「老年代只涨不跌」,基本就是内存泄漏

二、第二步:定位泄漏类型(区分堆内/堆外)

绝大多数泄漏是 堆内泄漏,极少数是堆外。

1. 堆内泄漏(95%场景)

  • 特征:老年代持续上涨
  • 原因:对象被长生命周期引用持有(静态集合、线程池、缓存、单例)

2. 堆外泄漏

  • 特征:堆内存正常,但进程物理内存持续涨
  • 原因:NIO DirectByteBuffer、JNI、池化资源未释放

先按堆内泄漏排查,效率最高。

三、第三步:无侵入抓取堆快照(核心步骤)

不需要重启、不需要压测、低影响,生产直接用

推荐抓取时机

  • 一次Full GC之后立即抓(最干净,能精准定位泄漏对象)
  • 或者内存涨到相对高位时抓

抓取命令

jmap -dump:format=b,file=leak.hprof <pid>
  • 生成 leak.hprof 二进制堆转储文件
  • 大小≈当前堆使用量,注意磁盘空间

小提示:JDK8+ 加 -dump:live 可以只抓存活对象,文件更小:

jmap -dump:live,format=b,file=leak.hprof <pid>

四、第四步:用工具分析泄漏(10分钟定位代码)

最常用、最稳定的工具:Eclipse MAT(免费、强大)

1. MAT 打开 hprof 文件

选择 Acquire Heap Dump / Open Heap Dump

2. 直接使用「泄漏嫌疑报告」

MAT 会自动生成:Leak Suspects Report
它会直接告诉你:

  • 哪个类占用内存最大
  • 被谁引用
  • 哪行代码导致泄漏

3. 核心分析 3 步

  1. Histogram(直方图)
    看对象实例数,找数量异常多的业务对象(你的自定义类)
  2. Merge Shortest Paths to GC Roots
    右键对象 → 查看 GC Root 引用链
  3. 排除弱引用/软引用
    只看 强引用链,泄漏一定在这里

泄漏对象的典型特征

  • 你的业务实体类(User/Order/Config等)实例数只增不减
  • static Map/List/Set 持有
  • 线程池 / 单例 / 缓存 持有
  • ThreadLocal 持有(高频泄漏点)

五、第五步:早期泄漏最常见的 5 种代码根因

你在分析时,优先排查这 5 类代码,90% 泄漏都在这里:

1. 静态集合(最常见)

private static List<Object> list = new ArrayList<>();
// 只add不remove,对象永远无法回收

2. ThreadLocal 未清理

private static ThreadLocal<Object> tl = new ThreadLocal<>();
// 线程池中的线程长期存活,ThreadLocalMap 一直持有对象

3. 线程池/自定义线程忘记关闭

  • 线程是 GC Root,线程里的引用全部泄漏
  • 线程池任务里持有大对象,且任务无限等待

4. 缓存未设置过期/淘汰

private static Map<String, Object> CACHE = new HashMap<>();
// 无过期、无淘汰,无限增长

5. IO/连接未关闭

  • InputStream、Socket、Connection 未 close
  • 底层持有堆外/堆内引用

六、无工具、命令行快速初筛(应急用)

如果不能用MAT,可以先用命令粗定位:

jmap -histo <pid> | head -20

输出:实例数、字节数、类名
自定义类、实例数异常高的类,就是怀疑对象。

七、最佳实践:提前发现早期泄漏

不用等问题爆发,配置监控即可:

  1. 监控 老年代使用率趋势
  2. 监控 GC后内存回落幅度
  3. 配置告警:老年代连续 10 次 GC 不下降
  1. 看趋势:老年代只涨不跌 = 泄漏
  2. 抓快照:Full GC 后 jmap 抓 hprof
  3. 用MAT:看 Leak Suspects
  4. 查引用:静态集合、ThreadLocal、线程池、缓存
  5. 看代码:谁长期持有对象不释放

以上就是Java内存泄漏早期排查流程(未触发频繁Full GC前)的详细内容,更多关于Java内存泄漏早期排查的资料请关注脚本之家其它相关文章!

相关文章

  • springboot如何使用MybatisPlus

    springboot如何使用MybatisPlus

    MyBatisPlus是一个强大的数据库操作框架,其代码生成器可以快速生成实体类、映射文件等,本文介绍了如何导入MyBatisPlus相关依赖,创建代码生成器,并配置数据库信息以逆向生成代码,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • 使用idea2017搭建SSM框架(图文步骤)

    使用idea2017搭建SSM框架(图文步骤)

    这篇文章主要介绍了使用idea2017搭建SSM框架(图文步骤),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • 手把手教你用Java实现一套简单的鉴权服务

    手把手教你用Java实现一套简单的鉴权服务

    现今大部分系统都会有自己的鉴权服务,本文介绍了最常用的鉴权服务,就是日常用户的登录登出,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Spring WebSocket 404错误的解决方法

    Spring WebSocket 404错误的解决方法

    这篇文章主要为大家详细介绍了Spring WebSocket 404错误的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • 如何解决IDEA使用Tomcat控制台中文出现乱码问题

    如何解决IDEA使用Tomcat控制台中文出现乱码问题

    这篇文章主要介绍了如何解决IDEA使用Tomcat控制台中文出现乱码问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Java合并两个相同的List集合的四种方法解析

    Java合并两个相同的List集合的四种方法解析

    这篇文章主要给大家介绍了关于Java合并两个相同的List集合的四种方法,在Java编程中常需合并两个List,常用addAll()方法,简单高效,Java8起可用Stream的concat()方法,并发场景下,CopyOnWriteArrayList类保证线程安全,Collections.union()合并且去重,需要的朋友可以参考下
    2024-10-10
  • 详解如何用spring Restdocs创建API文档

    详解如何用spring Restdocs创建API文档

    这篇文章将带你了解如何用spring官方推荐的restdoc去生成api文档。具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Java线程重复执行以及操作共享变量的代码示例

    Java线程重复执行以及操作共享变量的代码示例

    这篇文章主要介绍了Java中对线程重复执行以及操作共享变量的代码示例,来自于Java面试题目的练习整理,需要的朋友可以参考下
    2015-12-12
  • SpringBoot之QueryDsl嵌套子查询问题

    SpringBoot之QueryDsl嵌套子查询问题

    这篇文章主要介绍了SpringBoot之QueryDsl嵌套子查询问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • mybatis中oracle实现分页效果实例代码

    mybatis中oracle实现分页效果实例代码

    实现分页的方式有很多,但常用的是通过SQL来显示分页。这篇文章主要介绍了mybatis中oracle实现分页效果实例代码,有兴趣的可以了解一下。
    2017-04-04

最新评论