JVM性能调优(六):线上系统的机器CPU负载过高常见场景

x33g5p2x  于2021-09-19 转载在 Java  
字(1.9k)|赞(0)|评价(0)|浏览(310)

1、线上系统的机器CPU负载过高的两个常见的场景

第一个场景,是你自己在系统里创建了大量的线程,这些线程同时并发运行,而且工作负载都很重,过多的线程同时并发运行就会导致 你的机器CPU负载过高。

第二个场景,就是你的机器上运行的JVM在执行频繁的Full GC,Full GC是非常耗费CPU资源的,他是一个非常重负载的过程。所以一旦你的JVM有频繁的Full GC,带来的一个明显的感觉,一个是系统可能时不时会卡死,因此Full GC会带来一定的“Stop the World”问题,一个是机器的CPU负载很高。

2、排查

        首先看一下JVM Full GC的频率,通过jstat也好,或者是监控平台也好,很容易看到现在Full GC的频率。如果Full GC频率过高,那么就是Full GC引起的CPU负载过高。

        那么如果JVM GC频率很正常呢?那就肯定是你的系统创建了过多线程在并发执行负载很重的任务了!

2.1、频繁Full GC问题

如果有频繁Full GC的问题,一般可能性有三个:

  • 内存分配不合理,导致对象频繁进入老年代,进而引发频繁的Full GC;
  • 存在内存泄漏等问题,就是内存里驻留了大量的对象塞满了老年代,导致稍微有一些对象进入老年代就会引发Full GC;
  • 永久代里的类太多,触发了Full GC
  • 还是有频繁Full GC,也许就是工程师错误的执行“System.gc()”导致的但是这个一般很少见,而且之前讲过,JVM参数中可以禁止这种显式触发的GC。
    一般排查频繁Full GC,核心的利器当然是jstat。

2.2、分析过程

  • 可以使用jmap+jhat的组合来分析内存里的大对象
  • 使用MAT
    因为jhat适合快速的去分析一下内存快照,但是功能上不是太强大,所以一般其实常用的比较强大的内存分析工具,就是MAT。

 

1)、先用jmap命令导出一份线上系统的内存快照即可

jmap -dump:format=b,file=文件名 [服务进程ID]

拿到了内存快照之后,其实就是一份文件,接着就可以用jhat、MAT之类的工具来分析内存了。

2)、MAT使用

下载好MAT后,在他的安装目录里,可以看到一个文件名字叫做:MemoryAnalyzer.ini

这个文件里的内容类似如下所示:
-startup

../Eclipse/plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
--launcher.library

../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.700.v20180518-1200
-vmargs

-Xmx1024m
-Dorg.eclipse.swt.internal.carbon.smallFonts

-XstartOnFirstThread

        大家务必要记得,如果dump出来的内存快照很大,比如有几个G,你务必在启动MAT之前,先在这个配置文件里给MAT本身设置一下堆内存大小,比如设置为4个G,或者8个G,他这里默认-Xmx1024m是1G。
        接着大家直接启动MAT即可,启动之后看到的界面中有一个选型是:Open a Heap Dump,就是打开一个内存快照的意思,选择他, 然后选择本地的一个内存快照文件即可。

3)、基于MAT来进行内存泄漏分析

        使用MAT打开一个内存快照之后,在MAT上有一个工具栏,里面有一个按钮,他的英文是:Leak Suspects,就是内存泄漏的分析。
        接着MAT会分析你的内存快照,尝试找出来导致内存泄漏的一批对象。

        这时明显可以看到他会显示给你一个大饼图,这里就会提示你说,哪些对象占用内存过大。
        这个时候直接会看到某种自己系统创建的对象占用量过大,这种对象的实例多达数十万个,占用了老年代一大半的内存空间。

        接着当然是找开发工程师去排查这个系统的代码问题了,为什么会创建那么多的对象,而且始终回收不掉?
这就是典型的内存泄漏!系统创建了大量的对象占用了内存,其实很多对象是不需要使用的,而且还无法回收掉。

        后来找到了一个原因,是在系统里做了一个JVM本地的缓存,把很多数据都加载到内存里去缓存起来,然后提供查询服务的时候直接从 本地内存走。
 

        但是因为没有限制本地缓存的大小,并且没有使用LRU之类的算法定期淘汰一些缓存里的数据,导致缓存在内存里的对象越来越多,进而造成了内存泄漏。
        解决问题很简单,只要使用类似EHCache之类的缓存框架就可以了,他会固定最多缓存多少个对象,定期淘汰删除掉一些不怎么访问的缓存,以便于新的数据可以进入缓存中。

相关文章