一次JVM_OLD区占用过高、频繁Full GC的解决过程【转】

转自:http://www.liuzk.com/294.html

最近,公司网站频繁报警,JVM_OLD占用过高,线上访问超时严重,针对这个问题着实头疼了一把,不过最终还是解决了,下面说下解决的过程。

1,首先 登到线上机器上去,top命令,查看当前机器的负载,查看当前哪个进程在消耗资源。

Shell

1

top

找到CPU或者内存占用过高的那个进程。发现有一个进程,CPU居高不下,保留进程id

2,top命令,继续跟踪该进程里的所有线程,找到占用CPU过高的线程。

Shell

1

top -Hp [进程ID]

 

top-hp

3,找到线程ID,正在消耗CPU,把线程ID转换为16进制,执行jstack命令保留当前java进程堆栈

Shell

1

jstack [进程ID] > jstack_01

然后,在该文件jstack_01 里搜索 线程id为[16进制的线程id]的线程,查看堆栈信息。

jstack_01

一般情况下,这三部就能找到消耗资源的线程的情况,看到该线程的运行堆栈信息,当时发现,其实中 nid=0x8d0f 的线程在占用cpu,堆栈里看到的是VM线程,VM线程的意思就是jvm线程在工作,看下面几个run的线程,都是gc线程,猜测,此时,jvm正在GC。

4、接下来就是确认当前JVM各分区的情况了,这里使用的是jstat

Shell

1

jstat -gcutil [进程id] 1000

每1000ms打印一次gc信息。

jstat_01

通过此图,可以发现,新生代eden区,survivor区,old区都已经100%,并且,jvm启动到现在,Full GC了978次,共花费时间12240.255秒,这样平均每次Full GC 12.5秒。

看来就是Full GC频繁并且过程漫长,导致线上机器卡死的问题了。下面解释下jstat的gc信息各个字段的含义:

Shell

1

2

3

4

5

6

7

8

9

10

S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

E 年轻代中Eden(伊甸园)已使用的占当前容量百分比

O old代已使用的占当前容量百分比

P perm代已使用的占当前容量百分比

YGC 从应用程序启动到采样时年轻代中gc次数

YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)

FGC 从应用程序启动到采样时old代(全gc)gc次数

FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)

GCT 从应用程序启动到采样时gc用的总时间(s)

5、确认是Full GC问题,那就只能看堆内存了,

Shell

1

jmap -histo [进程id] > jmap.txt

 

该命令可以查看当前jvm内存里对象的实例数和占用内存数

【注意】该命令可能会造成线上程序假死,会卡死一段时间,所以线上程序谨慎使用!!!

jmap_01

从结果里可以看出,占内存最多的是前两个,一个是char,一个是string

所以有以下猜测:

1)程序里有内存缓存,缓存的是字符串,内存缓存逐渐增多,逐步移步老年代,最终导致爆满。

2)有大量拼接字符串的地方,

3)static的变量,存储大量的字符串,排名第六的是hashMap,猜想可能是有static的 hashMap??

继续分析该文件,看到,有个 com.bj58.jwdf.framework.cache.ConcurrentCacheProvider 对象,有10w多个实例,通过看代码,的确这个是一个自己实现的内存缓存,果断想办法优化。

优化上线后,发现内存问题虽然有所缓解,但是还是会有jvm爆满的时候。

这样在继续分析,就比较困难了,然后同时优化了下jvm启动的参数,添加full gc的时机,

 

Shell

1

-XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly

保证,在old区达到80%的时候就开始执行Full GC。

添加:

Shell

1

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log

打印gc信息,以便保留证据。

并且在代码里查找一些其他的公共的static 的map 以及 内存缓存的地方,统统优化了,上线后还是不理想。

最后没办法了,只能dump堆内存快照了,用工具分析了。

修改jvm启动内存,改小一些,以便在满的时候打的快照能在本地分析,,

Shell

1

jmap -dump:format=b,file=aa.bin 1232134

改命令打印出堆内存快照,我把jvm内存改成最大6G,下载的快照文件,足足有6.5G,然后下载MAT工具进行分析。

 

转载请注明:刘召考的博客 » 一次JVM_OLD区占用过高、频繁Full GC的解决过程

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM(Java虚拟机)的老年代是指JVM中的内存域,用于存储生命周期较长的对象。在JVM中,内存被划分为多个不同的域,其中包括年轻代和老年代。老年代主要用于存放经过多次垃圾回收后幸存下来的对象。 老年代的对象有以下特点: 1. 生命周期较长:老年代存放的对象通常是经过多次垃圾回收后仍然存活的对象,它们的生命周期相对较长。 2. 大对象:老年代通常会存放较大的对象,例如大型集合、数组等。这些对象占用的内存空间较多。 3. 垃圾回收较少:由于老年代存放的对象生命周期较长,因此老年代的垃圾回收相对较少。但是当老年代空间不足时,JVM会触发一次较为耗时的Full GC来进行垃圾回收。 为了优化老年代的性能和空间利用率,JVM引入了一些技术: 1. 分代回收JVM采用分代回收策略,将内存划分为年轻代和老年代,以便更好地适应不同对象的生命周期。 2. 大对象处理:JVM针对较大的对象引入了特殊的处理方式,例如将大对象直接分配到老年代。 3. 垃圾回收算法:JVM使用不同的垃圾回收算法来对老年代进行垃圾回收,例如标记-清除算法、标记-整理算法等。 总之,老年代是JVM中用于存放生命周期较长的对象的内存域。通过采用分代回收策略和针对大对象的处理方式,JVM能够更好地管理和优化老年代的性能和空间利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值