记一次java应用内存不断增长问题解决过程

1、在解决问题之前,先看下jvm堆内存结构,如下图所示:

对于Java应用,虚拟机管理的内存,可以参考如下图所示:

一般对于一个应用来说,如果内存使用过大,可以从两块来分析,第一:堆内存,第二:堆外内存。下面着重从这两个方面阐述下对最近遇到的内存问题进行分析并解决的过程。

问题详情:

本人负责了一个应用,主要就是定时调度一批接口任务,同时提供一些对外接口,功能很简单,在使用的过程中,逐渐发现了几个内存问题。

1、oom问题,发现一段时间后,应用进程就没了,查看日志提示内存溢出,内存被吃光了,不够分配下个资源使用的空间了。

2、第一个问题解决后,添加了几个功能,发现又出现一些问题:应用运行一段时间后,发现,内存随着时间不段推移,内存占用也越来越多,一般4天后,内使用几乎达到90%,很吓人。

第一个问题解决过程:

oom问题,一般就是程序代码处理不当造成的,有些资源没有释放或者一直在循环开辟某些内存资源。分析这个问题,我使用了本地jvm的一个工具,找到对应的jdk/bin/jvisualvm 工具,它是用来JVM监测、故障排除、分析工具,主要以图形化界面的方式提供运行于指定虚拟机的Java应用程序的详细信息。

1、本地启动程序后,同时打开jvisualvm,在jvisualvm工具可视化页面有多个选项tab,可以根据需求,选择对应的tab进行使用,而我主要对profiler进行了使用,同时发现了问题,进而解决了问题,如下图所示:

这里排查问题,主要对快照功能进行了使用,每隔一段时间进行一次快照(每次快照前,先进行一次手动gc),然后两次快照进行对比差值,进而发现内存不断增长的是哪些对象。

如下图所示,选择第一张和最后一张对比图:

通过以上方法,不断去尝试,一般几个小时候,就能对比出哪些是一直占用内存,且gc也不生效的对象,从而定位类名以及方法,去解决即可。下图是解决后内存的使用情况:

运行个半个月是没有问题的,后面也没有出现oom。

第二个问题解决过程

第二个问题,不好定位,解决的时间比较长,需要长时间不断观察,才会彻底解决。内存一直再涨,但是涨的速度并没有很快,所以用第一种方法不大好分析,因为本地数据并不多,不好分析。通过后面的分析结果,这里涉及两块内存:堆内存,堆外内存。

这次内存不断增长问题,并没有导致应用不可用,出现oom等现象,纯粹是自己隔段时间观察的,下面是解决问题的过程。

1、jvm参数优化

应用运行一段时间,对应用java堆内存的使用进行观察,这里要清楚堆内存结构,通过命令:

jmap -heap pid,可以查看堆内存使用情况,比如:

排查内存问题,jvm优化也能解决一部分问题,所以我先通过优化,再进一步观察,优化的原则,可以参考下图:

通过jvm优化,在gc时间可接受的情况下,配置jvm参数,在观察一段时间。

2、堆内存使用分析

通过jvm优化后,使用命令 jstat -gc pid,查看gc次数,发现gc时间相比之前高了一些,因为应用对实时性要求不高,可以接受,xmx配置比之前多了一倍,应用运行时间长了很多,但是还是会发现,内存在涨的问题,自己继续分析。

通过top命令发现,运行几天后,res内存超过配置的xmx,随着时间推移,res还是再涨。通过jmap -heap pid命令查看堆内存使用情况,发现堆内存占用并不多,远小于res值,说明这里res的内存值多半是堆外内存在占用,于是开始转战对堆外内存的分析。

3、堆外内存分析

在分析堆外内存时,在网上搜了很多。在分析堆外内存时,使用到了jcmd命令,Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据。

a、在使用jcmd之前,需要配置一些jvm参数,如下如所示:

即:-XX:NativeMemoryTracking=[off | summary | detail] 配置该jvm参数,重启应用即可。

b、通过以上操作,使用命令 jcmd pid VM.native_memory detail scale=MB > a.txt 查看内存的一个使用情况,如下图所示:

这里包含了几个部分,在分析这些内存的时候,需要搞明白啥意思,具体可以参考文章:

https://blog.csdn.net/Developlee/article/details/100691997

c、搞清楚每一项代表的含义,那接下来就是结合自己的代码,分析哪块可能有问题。对于我问题,通过 jcmd pid VM.native_memory baseline 设置基准线,耐心多次 jcmd pid VM.native_memory detail.diff scale=MB > a_diff.txt 看差值,然后得出有可能哪里是问题所在,比如下面是差值的一个截图,这是未解决问题前的一个比较,问题很明显:

从这里就可以看出,线程一直在增加,且资源一直未释放,但是有哪些线程一直在创建申请资源呢,一时也摸不着头绪,所以要知道哪些线程,需要进一步分析。

d、在分析以上问题时,需要查看dump文件,可以通过Jprofile 或者 eclipse mat来分析内存占用情况。

通过jmap -dump pid > m.hprof 发现文件实在是太大,达到3g多,而且还占用着内存,根本从linux上转移不出来,也就无法通过本地idear插件Jprofile 查看,于是使用工具MemoryAnalyzer,根据系统属性,官网下载后,解压即可。

官网地址:http://www.eclipse.org/mat/downloads.php

下载后将包传到linux服务器上解压。

MemoryAnalyzer.ini 配置文件可以修改最大的内存,默认1G基本够用了。

我在使用后,发现java不可用,所以在上面基础上需要配置下面的两部分:-vm -startup,下面是我后来添加的配置:

然后,在linux执行分析命令执行命令

./ParseHeapDump.sh m.hprof  org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components。

m.hprof就是jvm的dump文件,在mat目录下会生成3份.zip结尾的报告和一些m.相关的文件,将生成的m.hprof相关的文件都下载到本地磁盘:

1)使用浏览器

解压缩以.zip结尾的文件,解压后

使用浏览器打开index.html文件内容,查看分析报告,结合前面的分析,着重看下thread的使用情况如下图:

异常thread1:

异常thread2:

 看到这里,其实如果对代码熟悉的话,立即知道哪里出问题了,后面修改后,应用发布,在继续观察。

e、按上面修改后,再次观察内存使用,整体发现,会比之前好很多,但是通过jcmd再次对比差值,thread还是再增加,于是再重复d步骤的分析方法,其实这里,如果本地有Jprofile的话,完全可以用该工具打开m.hprof,这次我用本地的Jprofile分析,如下图所示,看到这里,基本知道是啥问题了,修改代码,再次发布,继续跟踪。

d、经过以上几个步骤,观察内存使用情况,发现整体优化非常明显,如下jcmd对比图:

看到这里,真实喜出望外,观察几天后,内存基本稳定了,目前运行良好。

内存问题分析路漫漫,希望这篇文章能够帮助到大家O(∩_∩)O哈哈~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章