用mat分析内存dump文件中unreachable objects

现象

  • 某个大版本上线后,某服务频繁报警: load过高,但很快就会恢复,即间歇性load过高,原因难以定位。

观察

  • 从报警系统观察报警时间分布,有一些周期性,但周期会在1小时到2小时之间,不稳定
    • 初步排除是定时任务导致
  • 观察gc日志,发现报警时间点与gc时间开销较高的点比较吻合,因此着重观察GC
  • 发现JVM GC相关的配置不是很合理,做了简单的优化:将old区的并行收集改为CMS,情况有所改善,但问题依然存在
  • 该现象是上线后才出现的,可以认定是业务逻辑所致。着重查看了业务逻辑的diff,因业务逻辑改动较多,未能直接找到原因。
  • 既然是GC所致,就先看看GC为何这么慢吧!

初步定位

  • 用jmap做了两份内存的dump,一份是old快满(用jstat来实时观察)即即将发生full gc之前的dump,另一份是GC刚刚结束的dump
    • 这样可以明显地对二者做对比。
  • 用mat打开(在机群上开了个vncserver,在本地用vncviewer连接上)两份dump,发现两份dump中reachable objects均只有400m不到,而unreachable objects有3G多
  • 观察unreachable objects,按shallow heap大小排序,忽略byte和char等基本类型(复杂类型数据都是由基本类型组成的)发现TreeMap占了300M+,让人怀疑。
  • 然而因为是unreachable的,无法直接查看这些TreeMap对象的引用。如何才有知道到底谁引用它们呢?在代码里grep了一下,发现自己项目中没有任何地方用到TreeMap,从而判断应该是第三方库用到的。
  • 从另外一个角度考虑,我可以先看一下reachable objects中TreeMap的引用。
  • 发现其中对treeMap有引用的对象中,个数较多的是JDBC4ResultSet和ShardedJedis。先看前者在unreachable objects中的情况,共有22158个垃圾对象。
  • 在mat的dominator tree中看JDBC4ResultSet的大小,可以知道每个JDBC4ResultSet的retained heap大小为4.928k(也有不同,但出入不大),从而22158个的retained heap大小为22158 * 5k 约100M,远小于heap区的大小,因此不是造成问题的原因,忽略。
  • 再看后者ShardedJedis,从unreachable objects中可以看到共有4214个垃圾对象。
  • 同时从dominator tree中可以看出每个ShardedJedis的Retained heap大小为323k,从而4214个的retained heap大小的4214*323k约为1.4G
  • 至此,可以基本认定sharedeJedis为主要的垃圾,load间歇性过高的原因正是因为这些垃圾快速的生成和回收所致。

进一步分析

  • 看一下我们对SharededJedisPool的配置:

      #最大分配的对象数
      redis.pool.maxActive=500
      #最大能够保持idel状态的对象数
      redis.pool.maxIdle=100
      #当池内没有返回对象时,最大等待时间
      redis.pool.maxWait=200
      #当调用borrow Object方法时,是否进行有效性检查
      redis.pool.testOnBorrow=false
      #当调用return Object方法时,是否进行有效性检查
      redis.pool.testOnReturn=false
    
  • 该配置与dump中的pool配置一致。

  • 通过netstat grep redisserver的端口,可以查到只有100个连接,即可预估在“够用”的情况下,redis的连接池中通常只会有100个左右(大多为空闲)

  • 那么问题来了:为什么会有这么多ShardedJedis的垃圾呢?

  • 我们redis连接池是用GenericObjectPool来管理的, 需要时从里面借(borrow object),用完了再还回去(return object)。如果借的时候不够,就会new一个SharededJedis来给借用者。同时还有一个清理机制,每隔一段时间去把多余的空闲对象清理掉。从上图中可以看出,这个清理机制的间隔时间_timeBetweenEvictionRunsMills是30000ms,即30s.

  • 进一步,这些ShardedJedis能度过Young GC并进入old区,必然是在young区gc时没有被清理,从而可推断该进程做一次young GC的周期会远小于GenericObjectPool的清理周期。用jstat –gctuil查看:

      [love@love21 ~]$ jstat -gcutil 21416 1000
        S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
        8.67   0.00  43.38  16.74  52.69  56012 2064.246   143   24.948 2089.193
        8.67   0.00  77.59  16.74  52.69  56012 2064.246   143   24.948 2089.193
        0.00   7.91   1.92  16.84  52.69  56013 2064.278   143   24.948 2089.225
        0.00   7.91  34.35  16.84  52.69  56013 2064.278   143   24.948 2089.225
        0.00   7.91  64.58  16.84  52.69  56013 2064.278   143   24.948 2089.225
        0.00   7.91  78.19  16.84  52.69  56013 2064.278   143   24.948 2089.225
        9.43   0.00   0.00  16.90  52.69  56014 2064.311   143   24.948 2089.258
        9.43   0.00  22.36  16.90  52.69  56014 2064.311   143   24.948 2089.258
        9.43   0.00  55.95  16.90  52.69  56014 2064.311   143   24.948 2089.258
        9.43   0.00  90.86  16.90  52.69  56014 2064.311   143   24.948 2089.258
        0.00   8.99  18.07  16.95  52.69  56015 2064.344   143   24.948 2089.292
        0.00   8.99  51.25  16.95  52.69  56015 2064.344   143   24.948 2089.292
        0.00   8.99  72.06  16.95  52.69  56015 2064.344   143   24.948 2089.292
        0.00   8.99  92.20  16.95  52.69  56015 2064.344   143   24.948 2089.292
        7.41   0.00  30.01  17.07  52.69  56016 2064.379   143   24.948 2089.326
        7.41   0.00  65.24  17.07  52.69  56016 2064.379   143   24.948 2089.326
        7.41   0.00  97.34  17.07  52.69  56016 2064.379   143   24.948 2089.326
    
  • 可以看到约5s左右就会进行一次young GC,与推断相符。

  • 现在,基本有了一些思路:1. 业务中有向GenericObjectPool borrow大量的object(应该远大于100)的逻辑,2. 并且很可能是定时性的任务(crontab 或 scheduleTask).

  • 结合上述两点并结合代码最新的改动,发现有地方将获取50个数据对象改为了获取250个,而为了提交性能,会用future模式开多个线程并发向一个ThreadPoolExecutor 提交大量的任务,并且该任务对redis有请求操作,而这个ThreadPoolExecutor的coreSize是200(注意,远大于redis pool的100):

      private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(200, 400, 3600, TimeUnit.SECONDS, 
              new LinkedBlockingQueue<Runnable>(500), new ThreadFactory() {
          
                  private AtomicInteger id = new AtomicInteger(0);
                  @Override
                  public Thread newThread(Runnable r) {
                      Thread thread = new Thread(r);
                      thread.setName("Love-" + id.addAndGet(1));
                      return thread;
                  }
              }, new ThreadPoolExecutor.CallerRunsPolicy());
    
  • 再追溯这个50到250的改动,实际上是可以很简单的做优化的。

  • 至此,问题定位到,优化并上线,不再频繁出现load过高的问题,即GC不再如此频繁

经验

  • 解决一个性能问题好比侦察案件,可以多采集利用分析各种信息,不要死磕。虽然最终发现问题很简单,并一开始就认定问题出在业务逻辑上,可几个人花了不少时间去查代码,都没能找到原因。(该问题被一些设计模式“隐藏”的较深)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容