用mat分析內(nèi)存dump文件中unreachable objects

現(xiàn)象

  • 某個大版本上線后倔喂,某服務(wù)頻繁報(bào)警: load過高铝条,但很快就會恢復(fù),即間歇性load過高席噩,原因難以定位班缰。

觀察

  • 從報(bào)警系統(tǒng)觀察報(bào)警時間分布,有一些周期性悼枢,但周期會在1小時到2小時之間埠忘,不穩(wěn)定
    • 初步排除是定時任務(wù)導(dǎo)致
  • 觀察gc日志,發(fā)現(xiàn)報(bào)警時間點(diǎn)與gc時間開銷較高的點(diǎn)比較吻合馒索,因此著重觀察GC
  • 發(fā)現(xiàn)JVM GC相關(guān)的配置不是很合理莹妒,做了簡單的優(yōu)化:將old區(qū)的并行收集改為CMS,情況有所改善绰上,但問題依然存在
  • 該現(xiàn)象是上線后才出現(xiàn)的旨怠,可以認(rèn)定是業(yè)務(wù)邏輯所致。著重查看了業(yè)務(wù)邏輯的diff蜈块,因業(yè)務(wù)邏輯改動較多鉴腻,未能直接找到原因。
  • 既然是GC所致百揭,就先看看GC為何這么慢吧爽哎!

初步定位

  • 用jmap做了兩份內(nèi)存的dump,一份是old快滿(用jstat來實(shí)時觀察)即即將發(fā)生full gc之前的dump器一,另一份是GC剛剛結(jié)束的dump
    • 這樣可以明顯地對二者做對比课锌。
  • 用mat打開(在機(jī)群上開了個vncserver,在本地用vncviewer連接上)兩份dump,發(fā)現(xiàn)兩份dump中reachable objects均只有400m不到祈秕,而unreachable objects有3G多
  • 觀察unreachable objects渺贤,按shallow heap大小排序雏胃,忽略byte和char等基本類型(復(fù)雜類型數(shù)據(jù)都是由基本類型組成的)發(fā)現(xiàn)TreeMap占了300M+,讓人懷疑癣亚。
  • 然而因?yàn)槭莡nreachable的丑掺,無法直接查看這些TreeMap對象的引用。如何才有知道到底誰引用它們呢述雾?在代碼里grep了一下,發(fā)現(xiàn)自己項(xiàng)目中沒有任何地方用到TreeMap吭露,從而判斷應(yīng)該是第三方庫用到的隶校。
  • 從另外一個角度考慮饲常,我可以先看一下reachable objects中TreeMap的引用。
  • 發(fā)現(xiàn)其中對treeMap有引用的對象中黍翎,個數(shù)較多的是JDBC4ResultSet和ShardedJedis。先看前者在unreachable objects中的情況艳丛,共有22158個垃圾對象匣掸。
  • 在mat的dominator tree中看JDBC4ResultSet的大小,可以知道每個JDBC4ResultSet的retained heap大小為4.928k(也有不同氮双,但出入不大)碰酝,從而22158個的retained heap大小為22158 * 5k 約100M,遠(yuǎn)小于heap區(qū)的大小戴差,因此不是造成問題的原因送爸,忽略。
  • 再看后者ShardedJedis暖释,從unreachable objects中可以看到共有4214個垃圾對象袭厂。
  • 同時從dominator tree中可以看出每個ShardedJedis的Retained heap大小為323k,從而4214個的retained heap大小的4214*323k約為1.4G
  • 至此,可以基本認(rèn)定sharedeJedis為主要的垃圾球匕,load間歇性過高的原因正是因?yàn)檫@些垃圾快速的生成和回收所致纹磺。

進(jìn)一步分析

  • 看一下我們對SharededJedisPool的配置:

      #最大分配的對象數(shù)
      redis.pool.maxActive=500
      #最大能夠保持idel狀態(tài)的對象數(shù)
      redis.pool.maxIdle=100
      #當(dāng)池內(nèi)沒有返回對象時,最大等待時間
      redis.pool.maxWait=200
      #當(dāng)調(diào)用borrow Object方法時亮曹,是否進(jìn)行有效性檢查
      redis.pool.testOnBorrow=false
      #當(dāng)調(diào)用return Object方法時橄杨,是否進(jìn)行有效性檢查
      redis.pool.testOnReturn=false
    
  • 該配置與dump中的pool配置一致。

  • 通過netstat grep redisserver的端口乾忱,可以查到只有100個連接讥珍,即可預(yù)估在“夠用”的情況下,redis的連接池中通常只會有100個左右(大多為空閑)

  • 那么問題來了:為什么會有這么多ShardedJedis的垃圾呢窄瘟?

  • 我們r(jià)edis連接池是用GenericObjectPool來管理的, 需要時從里面借(borrow object)衷佃,用完了再還回去(return object)蹄葱。如果借的時候不夠氏义,就會new一個SharededJedis來給借用者锄列。同時還有一個清理機(jī)制,每隔一段時間去把多余的空閑對象清理掉惯悠。從上圖中可以看出邻邮,這個清理機(jī)制的間隔時間_timeBetweenEvictionRunsMills是30000ms,即30s.

  • 進(jìn)一步克婶,這些ShardedJedis能度過Young GC并進(jìn)入old區(qū)筒严,必然是在young區(qū)gc時沒有被清理,從而可推斷該進(jìn)程做一次young GC的周期會遠(yuǎn)小于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左右就會進(jìn)行一次young GC鸭蛙,與推斷相符。

  • 現(xiàn)在筋岛,基本有了一些思路:1. 業(yè)務(wù)中有向GenericObjectPool borrow大量的object(應(yīng)該遠(yuǎn)大于100)的邏輯娶视,2. 并且很可能是定時性的任務(wù)(crontab 或 scheduleTask).

  • 結(jié)合上述兩點(diǎn)并結(jié)合代碼最新的改動,發(fā)現(xiàn)有地方將獲取50個數(shù)據(jù)對象改為了獲取250個睁宰,而為了提交性能肪获,會用future模式開多個線程并發(fā)向一個ThreadPoolExecutor 提交大量的任務(wù),并且該任務(wù)對redis有請求操作柒傻,而這個ThreadPoolExecutor的coreSize是200(注意孝赫,遠(yuǎn)大于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的改動,實(shí)際上是可以很簡單的做優(yōu)化的诅愚。

  • 至此寒锚,問題定位到,優(yōu)化并上線违孝,不再頻繁出現(xiàn)load過高的問題刹前,即GC不再如此頻繁

經(jīng)驗(yàn)

  • 解決一個性能問題好比偵察案件,可以多采集利用分析各種信息雌桑,不要死磕喇喉。雖然最終發(fā)現(xiàn)問題很簡單,并一開始就認(rèn)定問題出在業(yè)務(wù)邏輯上校坑,可幾個人花了不少時間去查代碼拣技,都沒能找到原因。(該問題被一些設(shè)計(jì)模式“隱藏”的較深)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耍目,一起剝皮案震驚了整個濱河市膏斤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邪驮,老刑警劉巖莫辨,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡沮榜,警方通過查閱死者的電腦和手機(jī)盘榨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟆融,“玉大人草巡,你說我怎么就攤上這事⌒退郑” “怎么了山憨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冕末。 經(jīng)常有香客問我萍歉,道長,這世上最難降的妖魔是什么档桃? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮憔晒,結(jié)果婚禮上藻肄,老公的妹妹穿的比我還像新娘。我一直安慰自己拒担,他們只是感情好嘹屯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著从撼,像睡著了一般州弟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上低零,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天婆翔,我揣著相機(jī)與錄音,去河邊找鬼掏婶。 笑死啃奴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雄妥。 我是一名探鬼主播最蕾,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼老厌!你這毒婦竟也來了瘟则?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤枝秤,失蹤者是張志新(化名)和其女友劉穎醋拧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趁仙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年洪添,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雀费。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡干奢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盏袄,到底是詐尸還是另有隱情忿峻,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布辕羽,位于F島的核電站逛尚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刁愿。R本人自食惡果不足惜绰寞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铣口。 院中可真熱鬧滤钱,春花似錦、人聲如沸脑题。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叔遂。三九已至他炊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間已艰,已是汗流浹背痊末。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旗芬,地道東北人舌胶。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像疮丛,于是被迫代替她去往敵國和親幔嫂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容