0

0

什么是内存泄漏?在Java中如何排查内存泄漏问题?

紅蓮之龍

紅蓮之龍

发布时间:2025-09-03 19:18:01

|

480人浏览过

|

来源于php中文网

原创

内存泄漏的常见迹象包括应用性能下降、频繁full gc、outofmemoryerror异常、系统资源占用飙升及部分功能异常。当java程序中存在未释放的内存引用时,对象无法被垃圾回收,导致内存使用持续增长。典型表现有:响应变慢、gc日志显示old区内存居高不下、堆内存使用率接近上限。结合jstat、jmap等jdk工具可初步排查,通过观察gc频率与堆内存变化,定位可疑对象,进一步分析heap dump以确定泄漏源头。

什么是内存泄漏?在java中如何排查内存泄漏问题?

内存泄漏,简单来说,就是程序在申请内存后,却忘记或者无法释放这部分内存,导致这些内存一直被占用,直到程序耗尽所有可用内存,最终崩溃。在Java里,这通常意味着一些对象本该被垃圾回收器(GC)回收,但由于某些引用链的存在,它们仍然被GC视为“可达”,从而无法被清理。这就像你租了个房间,用完了却忘了退租,房间费还在一直扣,直到你破产。

解决方案

排查Java内存泄漏,我个人觉得,更像是一场侦探游戏,需要耐心和一些趁手的工具

第一步,也是最直观的,是观察系统表现。如果你的应用开始变得越来越慢,响应时间延长,或者时不时地抛出

OutOfMemoryError
,那基本可以确定,内存泄漏就在不远处等着你。这时候,我会先用一些基础的命令行工具,比如
jstat -gc <pid> 1000
来观察GC的活动情况。如果发现Full GC变得异常频繁,或者堆内存使用率持续高企不下,那这就是一个很强的信号。

接下来,真正的“武器”就派上用场了:内存分析工具。我最常用的,也是我个人觉得上手最快的,是VisualVM。你可以用它连接到你的Java进程,实时监控堆内存、GC活动。更关键的是,它可以方便地触发Heap Dump。一个Heap Dump,就好比是程序在某一刻的内存快照,里面记录了所有对象的详细信息。

立即学习Java免费学习笔记(深入)”;

拿到Heap Dump后,无论是用VisualVM自带的分析器,还是更专业的MAT (Eclipse Memory Analyzer Tool),甚至是JProfiler或YourKit,分析的思路都是相似的。我们主要关注以下几点:

智简简历
智简简历

免费AI简历制作工具,智能生成、可视化编辑、多格式导出。

下载
  • 大对象(Dominator Tree):找到那些占据了大量内存的对象,或者说,那些“支配”了其他大量对象的对象。它们往往是泄漏的源头。
  • GC Roots路径:对于那些被怀疑泄漏的对象,查看它们到GC Roots的引用路径。只要存在一条到GC Roots的强引用路径,对象就不会被回收。我们需要找到这条路径,并理解为什么它不应该存在。
  • 对象数量和类型:看看是否有某个类的实例数量异常增多,或者一些本该是短生命周期的对象却存活了很长时间。
  • 浅层大小(Shallow Size)和保留大小(Retained Size):浅层大小是对象自身占用内存,保留大小是对象自身加上它所持有的、且只被它持有的所有对象的内存总和。保留大小大的对象,往往是泄漏的“元凶”。

分析出可疑对象和引用路径后,就需要回溯代码了。这通常是最烧脑的一步。我一般会根据Heap Dump中显示的对象类型和引用链,在代码中搜索相关的类和逻辑。常见的泄漏模式包括:

  • 静态集合:比如一个静态的
    HashMap
    ArrayList
    ,不断地往里面添加对象,但从不移除。
  • 未注销的监听器或回调:如果你注册了一个监听器,但没有在合适的时机取消注册,那么被监听的对象可能会一直持有监听器的引用,导致监听器对象无法被回收。
  • 内部类引用外部类:非静态的内部类会隐式持有外部类的引用。如果内部类的实例生命周期比外部类长,就可能导致外部类无法被回收。
  • ThreadLocal未清理
    ThreadLocal
    在线程池中尤其容易出问题,如果线程复用而没有手动调用
    remove()
    ,旧值可能会一直存在。
  • 资源未关闭:虽然不是严格意义上的Java堆内存泄漏,但像文件句柄、数据库连接等外部资源未关闭,也会导致系统资源耗尽,表现类似内存泄漏。

通过工具定位问题,再结合代码分析,通常就能找到内存泄漏的根源并加以修复。这过程可能需要反复几次,才能彻底解决。

内存泄漏的常见迹象有哪些?

说实话,内存泄漏这东西,它不会直接告诉你“我泄漏了!”。它更多的是通过一些“症状”来暗示你。最直接的,当然是你的应用突然抛出

java.lang.OutOfMemoryError: Java heap space
异常,这基本上就是内存耗尽的铁证了。但在此之前,通常会有一些预兆:

  • 应用性能逐渐下降:随着运行时间的增长,应用的响应速度越来越慢,操作卡顿。这往往是因为GC为了清理内存,不得不频繁暂停应用线程,导致吞吐量下降。
  • 频繁的Full GC:如果你观察到GC日志中Full GC的频率异常高,并且每次GC后内存使用率并没有显著下降,或者很快又涨了上去,那很可能就是有大量“垃圾”对象无法被回收。
  • 系统资源占用飙升:不仅仅是Java堆内存,有时你会发现整个服务器的内存使用率都在不断攀升,即使Java进程本身没有直接占用那么多,也可能是因为GC频繁,导致CPU使用率过高,间接影响系统。
  • 部分功能出现异常或不可用:比如一个缓存功能,本该有淘汰策略,结果缓存越来越大,导致查询变慢甚至崩溃。或者某个服务接口开始返回错误,因为内部某个集合已经塞满了对象。 这些迹象单独出现可能不一定是内存泄漏,但如果组合出现,那排查内存泄漏就应该提上日程了。

Java中导致内存泄漏的常见陷阱和代码模式是什么?

在我多年的开发经验里,Java的内存泄漏,很多时候都和一些“想当然”或者“图方便”的代码习惯有关。以下是一些我经常遇到的陷阱:

  • 静态集合的滥用与遗忘:这是最经典的。比如一个
    static List<Object> cache = new ArrayList<>();
    ,你不断往里面加对象,却从不清理。这些对象因为被静态引用,永远不会被GC回收。即使你认为它们是“缓存”,也应该有明确的淘汰策略,或者考虑使用
    WeakHashMap
    等。
  • 未注销的监听器和回调:当你为一个UI组件、一个事件源或者一个服务注册了监听器(Listener)或回调(Callback)时,如果这个事件源的生命周期比监听器长,而你又忘记在合适的时机(比如组件销毁时)取消注册,那么事件源就会一直持有监听器的强引用,导致监听器对象无法被回收。这在Android开发中尤为常见。
  • 内部类对外部类的隐式引用:非静态的内部类(包括匿名内部类)会隐式地持有其外部类的引用。如果一个非静态内部类的实例被传递给了一个生命周期更长的对象,那么外部类的实例也可能因此无法被回收。一个常见的例子是,在Activity中创建了一个非静态的Handler,而Handler的消息队列中又持有Activity的引用,导致Activity无法被回收。
  • ThreadLocal
    的“陷阱”
    ThreadLocal
    本身是为了解决线程安全问题,但如果在使用线程池时,没有在任务结束后显式调用
    ThreadLocal.remove()
    来清理线程局部变量,那么当线程被回收并复用时,上一个任务设置的值可能仍然存在,并且如果这个值是一个大对象,就可能导致内存泄漏。
  • 资源未关闭:虽然Java的垃圾回收机制处理了堆内存,但对于文件流(
    FileInputStream
    ,
    FileOutputStream
    )、网络连接(
    Socket
    ,
    Connection
    )、数据库连接(
    Connection
    ,
    Statement
    ,
    ResultSet
    )等系统资源,你需要手动关闭。如果忘记关闭,这些资源会一直被操作系统占用,最终导致资源耗尽。Java 7引入的
    try-with-resources
    语句就是为了解决这个问题,强烈推荐使用。
  • 缓存机制的过度自信:自己实现缓存时,如果只考虑了“加”而没有考虑“淘汰”或“过期”,或者淘汰策略设计不当,很容易让缓存变成一个无限膨胀的黑洞。

这些模式,一旦不注意,就可能成为内存泄漏的温床。所以,在写代码的时候,多思考一下对象的生命周期和引用关系,很有必要。

如何利用JDK自带工具进行初步的内存泄漏排查?

JDK自带的工具,虽然可能没有JProfiler或YourKit那么华丽和功能强大,但它们在初步排查和定位问题上,绝对是够用的,而且还不用额外安装,非常方便。

  • jps
    (Java Virtual Machine Process Status Tool)
    :这是你排查的第一步,用来查看当前系统上运行的所有Java进程的PID。知道了PID,才能对症下药。
    jps -l
  • jstat
    (JVM Statistics Monitoring Tool)
    :这个工具可以用来监控JVM的GC活动和堆内存使用情况。
    jstat -gcutil <pid> 1000
    :每秒打印一次GC统计信息,包括Young/Old/Metaspace代的利用率、GC次数和时间。如果你看到Old区(或Metaspace)利用率持续走高,或者Full GC频率异常,那就是个很强的信号。
    jstat -gc <pid> 1000
    :更详细的GC信息。
  • jmap
    (Memory Map for Java)
    :这是获取堆内存信息的利器。
    jmap -heap <pid>
    :打印堆内存的概要信息,包括GC算法、堆配置参数以及各代的内存使用情况。
    jmap -histo:live <pid>
    :打印堆中对象的直方图,显示每个类的实例数量和内存占用
    live
    参数表示只统计存活对象。如果你发现某个类的实例数量异常多,或者占用了大量内存,那它就可能是泄漏的嫌疑犯。
    jmap -dump:format=b,file=heapdump.hprof <pid>
    :这是最关键的,它会生成一个二进制格式的堆转储文件(Heap Dump)。这个文件包含了JVM某一时刻所有对象的详细信息,是后续使用MAT、VisualVM等工具进行深度分析的原始数据。
  • jstack
    (Stack Trace for Java)
    :虽然
    jstack
    主要是用来查看线程堆栈,排查死锁或线程阻塞问题,但有时也能间接帮助我们。如果某个线程因为持有大量资源而长时间阻塞,或者大量线程都处于等待状态,可能也暗示着资源未释放或处理不当,虽然不是

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
eclipse教程
eclipse教程

php中文网为大家带来eclipse教程合集,eclipse是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。php中文网还为大家带来eclipse的相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

194

2023.06.14

eclipse怎么设置中文
eclipse怎么设置中文

eclipse设置中文的方法:除了设置界面为中文外,你还可以为Eclipse添加中文插件,以便更好地支持中文编程。例如,你可以安装EBNF插件来支持中文变量名,或安装Chinese Helper来提供中文帮助文档。本专题为大家提供eclipse设置中文相关的各种文章、以及下载和课程。

807

2023.07.24

c语言编程软件有哪些
c语言编程软件有哪些

c语言编程软件有GCC、Clang、Microsoft Visual Studio、Eclipse、NetBeans、Dev-C++、Code::Blocks、KDevelop、Sublime Text和Atom。更多关于c语言编程软件的问题详情请看本专题的文章。php中文网欢迎大家前来学习。

623

2023.11.02

Eclipse版本号有哪些区别
Eclipse版本号有哪些区别

区别:1、Eclipse 3.x系列:Eclipse的早期版本,包括3.0、3.1、3.2等;2、Eclipse 4.x系列:Eclipse的最新版本,包括4.0、4.1、4.2等;3、Eclipse IDE for Java Developers等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

180

2024.02.23

eclipse和idea有什么区别
eclipse和idea有什么区别

eclipse和idea的区别:1、平台支持;2、内存占用;3、插件系统;4、智能代码提示;5、界面设计;6、调试功能;7、学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

152

2024.02.23

eclipse设置中文全教程
eclipse设置中文全教程

本专题整合了eclipse设置中文相关教程,阅读专题下面的文章了解更多详细操作。

116

2025.10.10

eclipse字体放大教程
eclipse字体放大教程

本专题整合了eclipse字体放大教程,阅读专题下面的文章了解更多详细内容。

154

2025.10.10

eclipse左边栏不见了解决方法
eclipse左边栏不见了解决方法

本专题整合了eclipse左边栏相关教程,阅读专题下面的文章了解更多详细内容。

120

2025.10.15

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 80.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号