jvm内存泄漏排查

本文详细介绍了Java中的内存溢出和内存泄漏,包括它们的定义、原因、表现及解决方法。内存溢出是程序请求内存超出可用空间,而内存泄漏则是对象分配后无法释放。内存泄漏积累可能导致内存溢出。文中还提供了一个实际的Java内存泄漏排查案例,通过jps、jstat和jmap等工具进行分析。解决这些问题需要优化代码和调整JVM内存设置。

一、内存溢出和内存泄露

一种通俗的说法。

1、内存溢出:你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,出现溢出。

2、内存泄漏:你用new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实例所持有导致 GC 不能回收,也就是该被释放的对象没有释放。点击此处查看内存泄漏更多说明。

1.1 内存溢出

java.lang.OutOfMemoryError,是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。查看<jvm内存泄漏介绍>更多说明。

产生原因

产生该错误的原因主要包括:

  • JVM内存过小。
  • 程序不严密,产生了过多的垃圾。

程序体现

一般情况下,在程序上的体现为

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
  • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
  • 代码中存在死循环或循环产生过多重复的对象实体。
  • 使用的第三方软件中的BUG。
  • 启动参数内存值设定的过小。

错误提示

此错误常见的错误提示:

tomcat:java.lang.OutOfMemoryError: PermGen space
tomcat:java.lang.OutOfMemoryError: Java heap space
weblogic:Root cause of ServletException java.lang.OutOfMemoryError
resin:java.lang.OutOfMemoryError
java:java.lang.OutOfMemoryError

解决方法

1)增加JVM的内存大小

对于tomcat容器,找到tomcat在电脑中的安装目录,进入这个目录,然后进入bin目录中,在window环境下找到bin目录中的catalina.bat,在linux环境下找到catalina.sh。
编辑catalina.bat文件,找到JAVA_OPTS(具体来说是 set "JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%")这个选项的位置,这个参数是Java启动的时候,需要的启动参数。
也可以在操作系统的环境变量中对JAVA_OPTS进行设置,因为tomcat在启动的时候,也会读取操作系统中的环境变量的值,进行加载。
如果是修改了操作系统的环境变量,需要重启机器,再重启tomcat,如果修改的是tomcat配置文件,需要将配置文件保存,然后重启tomcat,设置就能生效了。

2)优化程序,释放垃圾

主要思路就是避免程序体现上出现的情况。避免死循环,防止一次载入太多的数据,提高程序健壮型及时释放。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。  

1.2 内存泄漏

Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点。

1)首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;
2)其次,这些对象是无用的,即程序以后不会再使用这些对象。

如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

关于内存泄露的处理页就是提高程序的健壮型,因为内存泄露是纯代码层面的问题。点击此处查看内存泄漏更多说明。

1.3 内存溢出和内存泄露的联系

内存泄露会最终会导致内存溢出。

相同点:都会导致应用程序运行出现问题,性能下降或挂起。
不同点:1) 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。2) 内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。

二、一个Java内存泄漏的排查案例

某个业务系统在一段时间突然变慢,我们怀疑是因为出现内存泄露问题导致的,于是踏上排查之路。

2.1确定频繁 FULL GC 现象

首先通过“虚拟机进程状况工具:jps”找出正在运行的虚拟机进程,最主要是找出这个进程在本地虚拟机的唯一ID(LVMID,Local Virtual Machine Identifier),因为在后面的排查过程中都是需要这个LVMID来确定要监控的是哪一个虚拟机进程。

同时,对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Process Identifier)是一致的,使用Windows的任务管理器或Unix的ps命令也可以查询到虚拟机进程的LVMID。

jps命令格式为:

jps [ options ] [ hostid ]

使用命令如下:

jps -l

使用ps:

ps aux | grep java

找到你需要监控的ID(假设为35644),再利用“虚拟机统计信息监视工具:jstat”监视虚拟机各种运行状态信息。

jstat命令格式为:

jstat [ option vmid [interval[s|ms] [count]] ]

使用命令如下:

jstat -gcutil 35644 1000

意思是每1000毫秒查询一次,一直查。gcutil的意思是已使用空间站总空间的百分比。

结果如下图:

jstat执行结果分析:

  • 这台服务器的新生代Eden区(E,表示Eden)使用了11.80%(最后)的空间
  • 两个Survivor区(S0、S1,表示Survivor0、Survivor1)分别是0%和0%
  • 老年代(O,表示Old)使用了4.16%
  • 程序运行以来共发生Minor GC(YGC,表示Young GC)16次,总耗时0.169秒,发生Full GC(FGC,表示Full GC)9次,Full GC总耗时1.121秒,总的耗时(GCT,表示GC Time)为1.29秒。 

2.2 找出导致频繁Full GC的原因

分析方法通常有两种:

1)把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。

2)更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件)。

jmap命令格式:

jmap [ option ] vmid

使用命令如下:

jmap -histo:live 35644

查看存活的对象情况,如下图所示:

存活对象

按照一位IT友的说法,数据不正常,十有八九就是泄露的。在我这个图上对象还是挺正常的。

我在网上找了一位博友的不正常数据,如下:

 2.3 定位到代码

定位带代码,有很多种方法,比如前面提到的通过MAT查看Histogram即可找出是哪块代码,也可以使用BTrace、Jprofile。

### JVM 内存泄漏排查方法与工具 JVM 内存泄漏是指程序在运行过程中,对象不再被使用但无法被垃圾回收器(GC)回收,导致内存资源被无效占用,最终可能引发 `OutOfMemoryError`。排查此类问题需要结合理论分析与工具辅助,以下是一些常用的排查方法和工具。 #### 1. 常见内存泄漏场景 - **集合类未释放引用**:如 `HashMap`、`ArrayList` 等集合类中保存了对象引用,使用完后未清空,导致 GC 无法回收。 - **监听器和回调未注销**:如事件监听器、定时任务等未及时取消注册。 - **缓存未清理**:长时间未访问的缓存对象未被清除,持续占用内存。 - **线程未终止**:线程未正确关闭,线程局部变量(`ThreadLocal`)未清理,导致内存泄漏。 #### 2. 内存查看与分析命令 JDK 提供了多种命令行工具用于查看 JVM 内存状态和堆转储信息: - **jstat**:用于监控 JVM 堆内存和垃圾回收情况。例如: ```bash jstat -gc <pid> 1000 ``` 该命令每秒输出一次 GC 统计信息,帮助判断内存回收是否正常进行。 - **jmap**:生成堆转储快照(heap dump),用于后续分析内存使用情况。例如: ```bash jmap -dump:live,format=b,file=heapdump.hprof <pid> ``` - **jcmd**:用于执行 JVM 命令,包括查看 Native Memory Tracking(NMT)信息。例如: ```bash jcmd <pid> VM.native_memory summary ``` 该命令可查看 JVM 原生内存使用情况,帮助识别非堆内存泄漏[^4]。 #### 3. 图形化分析工具 - **VisualVM**:集成了多种监控和分析功能,支持查看堆内存、线程状态、GC 情况,并可加载 heap dump 进行对象分析。 - **Eclipse MAT(Memory Analyzer)**:专门用于分析 heap dump 文件,可识别内存泄漏的潜在对象,提供支配树(Dominator Tree)和直方图(Histogram)视图。 - **JProfiler**:商业工具,提供更强大的性能分析功能,支持实时监控内存分配、GC 情况,并可进行线程和锁分析。 #### 4. 分析步骤示例 1. **监控内存使用情况**:使用 `jstat` 或 VisualVM 监控堆内存和 GC 情况,判断是否存在频繁 Full GC。 2. **生成堆转储文件**:使用 `jmap` 生成 heap dump。 3. **加载并分析 heap dump**:使用 Eclipse MAT 或 VisualVM 加载 dump 文件,查找内存占用最高的类及其引用链。 4. **定位泄漏点**:通过支配树或直方图视图,找到未被释放的对象及其持有者,分析引用关系,判断是否为内存泄漏。 #### 5. 预防措施 - 使用弱引用(`WeakHashMap`)管理缓存或监听器。 - 在对象使用完成后显式置 `null`,帮助 GC 回收。 - 定期进行内存分析,结合监控工具(如 Prometheus + Grafana)进行长期观察。 - 设置合理的 JVM 启动参数,如 `-Xmx` 和 `-Xms`,避免内存过小。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

magic_kid_2010

你的支持将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值