時(shí)間: 2018年3月16號(hào)晚
表現(xiàn)現(xiàn)象: 客戶訪問(wèn)非常慢到最后無(wú)法打開(kāi)
pinpoint請(qǐng)求: 請(qǐng)求逐步變慢(圖1),持續(xù)Full GC&CPU占用率高(圖2)暴匠,出現(xiàn)OOM(圖3)
緊急措施: 加大內(nèi)存竭缝、重啟服務(wù)器
重現(xiàn)問(wèn)題: 測(cè)試環(huán)境模擬并發(fā)訪問(wèn)(10線程&10000次/線程)房维,開(kāi)jmx端口&Virtual VM監(jiān)控(圖4),問(wèn)題重現(xiàn)抬纸,啟動(dòng)兩三分鐘后開(kāi)始出現(xiàn)GC咙俩,后續(xù)內(nèi)存持續(xù)上漲,dump堆轉(zhuǎn)儲(chǔ)文件
分析問(wèn)題: MAT(Memory Analyzer tool)分析湿故,找泄露的代碼
從MAT的分析可以看出阿趁,
javax.crypto.JceSecurity
的實(shí)例占用了最多的堆內(nèi)存(Retained Heap | 深堆) (圖5)從dominated tree可以看到,
javax.crypto.JceSecurity
的實(shí)例的retained heap占了73% (圖6)坛猪,主要是一個(gè)IdentityHashMap類型的屬性verificationResults脖阵,放了很多org.bouncycastle.jce.provider.BouncyCastleProvider
對(duì)象, 每個(gè)的retained heap占用182712字節(jié) (圖7)另外從virtual vm看轉(zhuǎn)儲(chǔ)堆上的線程墅茉, 有很多BLOCKED的線程命黔, 都卡在
javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:173)
(圖8),按現(xiàn)象看應(yīng)該是在等待Full GC從getVerificationResult可以看出就斤, 只要新傳入Provider都會(huì)放到verificationResults緩存起來(lái)悍募,
看調(diào)用鏈上(Rsa的decrypt 圖11-> Cipher.getInstance 圖12 -> JceSecurity.getVerificationResult 圖13),Rsa的解密不應(yīng)該每次重新new org.bouncycastle.jce.provider.BouncyCastleProvider
對(duì)象战转。
a. provider自己是一個(gè)java.util.Properties,將所有的預(yù)設(shè)的provider的key槐秧, value都作為property put到自己的hashtable里啄踊;
b. 如果key, value都是string的話, 還將他們放到一個(gè)java.util.LinkedHashMap.LinkedHashMap<String,String>()
的屬性 legacyStrings里刁标;
c. 在java.security.Provider.getService(String, String)
的時(shí)候颠通, 會(huì)把legacyStrings里所有的key,value解析成ServiceKey,Service對(duì),放到另一個(gè)java.util.LinkedHashMap.LinkedHashMap<ServiceKey,Service>()
的屬性legacyMap里
d. 因此膀懈,每次new BouncyCastleProvider 都會(huì)產(chǎn)生非常多的對(duì)象和引用(占用182712字節(jié))顿锰,且緩存在JceSecurity的verificationResults沒(méi)法釋放。
解決問(wèn)題: 按照BouncyCastleProvider 的注釋启搂,改代碼硼控,將BouncyCastleProvider實(shí)例對(duì)象作為Rsa的靜態(tài)成員變量可以解決問(wèn)題。
驗(yàn)證上線: 更改后再驗(yàn)證(圖14)胳赌,內(nèi)存使用正常牢撼,CPU正常
備 注: 其實(shí), 在重現(xiàn)問(wèn)題疑苫,設(shè)置jmx端口熏版,用jmeter測(cè)試的時(shí)候,已經(jīng)開(kāi)始在看代碼捍掺, 因?yàn)樽罱泳椭患恿私饷艿拇a撼短,除了
Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
這句,其他地方看不出來(lái)會(huì)出現(xiàn)內(nèi)存泄露挺勿,于是曲横,順著看下去,就看到javax.crypto.JceSecurity.getVerificationResult
里的緩存不瓶,回頭看了下BouncyCastleProvider就確定懷疑正確禾嫉。 然后緊急版本線上了再說(shuō)。virtual vm和MAT的截圖都是后續(xù)再分析時(shí)候截的湃番。
<span id="圖1">圖1</span>
<span id="圖2">圖2</span>
<span id="圖3">圖3</span>
<span id="圖4">圖4</span>
<span id="圖5">圖5</span>
<span id="圖6">圖6</span>
<span id="圖7">圖7</span>
<span id="圖8">圖8</span>
<span id="圖9">圖9</span>
<span id="圖10">圖10</span>
<span id="圖11">圖11</span>
<span id="圖12">圖12</span>
<span id="圖13">圖13</span>
<span id="圖14">圖14</span>