這是第一次把問(wèn)題分析的總結(jié)記錄下來(lái)终议,一是記錄下做備忘,二是把問(wèn)題分析的過(guò)程和總結(jié)梳理下葱蝗。
一共在兩個(gè)系統(tǒng)碰過(guò)因?yàn)榧用軐?dǎo)致OOM的問(wèn)題:
第一次遇到這個(gè)問(wèn)題的時(shí)候什么也不懂穴张,只知道渾身發(fā)抖心亂跳……。不知道問(wèn)題產(chǎn)生的原因更不知道該從何查起两曼,運(yùn)維同事給打了份dump日志皂甘,對(duì)我來(lái)說(shuō)什么用都沒(méi)有。沒(méi)辦法只能請(qǐng)當(dāng)時(shí)組里的牛人幫看悼凑。然后他就告訴我把一個(gè)變量設(shè)置成靜態(tài)的偿枕,修改后,發(fā)布到服務(wù)器上果然沒(méi)有再內(nèi)存飆升直至OOM了户辫。當(dāng)時(shí)也沒(méi)有請(qǐng)教下問(wèn)題的根本原因是什么渐夸,只是問(wèn)題解決就松了一口氣。
第二次是另外一個(gè)系統(tǒng)渔欢,但是那個(gè)系統(tǒng)不像第一次碰到的系統(tǒng)那樣發(fā)布上去碰到訪問(wèn)高峰就OOM墓塌。這個(gè)系統(tǒng)問(wèn)題發(fā)現(xiàn)的比較有意思,為什么說(shuō)有意思呢奥额?因?yàn)閱?wèn)題一直都存在苫幢,只不過(guò)加密工具類(lèi)調(diào)用的次數(shù)少,再加上這個(gè)系統(tǒng)發(fā)布比較頻繁垫挨,所以一直沒(méi)有OOM韩肝。直到有一次半個(gè)月沒(méi)有更新發(fā)布才報(bào)了OOM。
后來(lái)開(kāi)始學(xué)習(xí)了解jvm棒拂,嘗試著去模擬重現(xiàn)當(dāng)時(shí)的場(chǎng)景伞梯,然后分析系統(tǒng)OOM的原因玫氢。兩次問(wèn)題的共同點(diǎn)都是多次調(diào)用加密類(lèi)導(dǎo)致的。所以問(wèn)題應(yīng)該就在這個(gè)加密類(lèi)谜诫。
模擬的代碼如下:
public static voidencrypt(){ try{ Cipher cipher = Cipher.getInstance("RSA", newBouncyCastleProvider()); // cipher.init(); }catch(NoSuchAlgorithmException e) { e.printStackTrace(); }catch(NoSuchPaddingException e) { e.printStackTrace(); } }
jvm參數(shù)設(shè)置:
-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/baitianxia/Documents/oom/heapdump.hprof
循環(huán)調(diào)用上面的方法就可以制造OOM了漾峡。
既然問(wèn)題可以重現(xiàn),下面就可以開(kāi)始分析問(wèn)題的原因了:
jvm參數(shù)設(shè)置的是當(dāng)OOM的時(shí)候打印heap dump到指定目錄喻旷。分析heap dump文件常用的工具是MAT(Memory Analyzer Tool)生逸。
1)用MAT打開(kāi)heapdump.hprof文件的截圖如圖1,看到占用內(nèi)存最大的是餅圖中的深藍(lán)色部分且预,點(diǎn)擊顯示JceSecurity類(lèi)槽袄,那么可以初步斷定問(wèn)題是由這個(gè)類(lèi)導(dǎo)致的;
2)點(diǎn)擊Actions下的Dominator Tree可以查看占用內(nèi)存最大的對(duì)象锋谐,點(diǎn)擊后如圖2遍尺。可以看到有一個(gè)IdentityHashMap存儲(chǔ)了大量的BouncyCastleProvider對(duì)象涮拗;
圖2
3)點(diǎn)擊JceSecurity-->List Objects-->with outgoing references顯示如圖3所示乾戏,可以看到是變量名為verificationResults的identityHashMap中存放了大量的BouncyCastleProvier,基本上就已經(jīng)找到導(dǎo)致問(wèn)題的原因了三热;
4)查看源碼鼓择,可以看到因?yàn)関erificationResults是靜態(tài)的,不會(huì)被GC就漾,所以隨著加密工具類(lèi)調(diào)用的次數(shù)增加呐能,verificationResults存儲(chǔ)的BouncyCastle也越來(lái)越多,最終導(dǎo)致OOM抑堡。
public static final Cipher getInstance(String var0, Provider var1) throws NoSuchAlgorithmException, NoSuchPaddingException {
if(var1 == null) {
throw new IllegalArgumentException("Missing provider");
} else {
Exception var2 = null;
List var3 = getTransforms(var0);
boolean var4 = false;
String var5 = null;
Iterator var6 = var3.iterator();
while(true) {
while(true) {
Cipher.Transform var7;
Service var8;
do {
do {
if(!var6.hasNext()) {
if(var2 instanceof NoSuchPaddingException) {
throw (NoSuchPaddingException)var2;
}
if(var5 != null) {
throw new NoSuchPaddingException("Padding not supported: " + var5);
}
throw new NoSuchAlgorithmException("No such algorithm: " + var0, var2);
}
var7 = (Cipher.Transform)var6.next();
var8 = var1.getService("Cipher", var7.transform);
} while(var8 == null);
if(!var4) {
Exception var9 = JceSecurity.getVerificationResult(var1);
if(var9 != null) {
String var12 = "JCE cannot authenticate the provider " + var1.getName();
throw new SecurityException(var12, var9);
}
var4 = true;
}
} while(var7.supportsMode(var8) == 0);
if(var7.supportsPadding(var8) != 0) {
try {
CipherSpi var13 = (CipherSpi)var8.newInstance((Object)null);
var7.setModePadding(var13);
Cipher var10 = new Cipher(var13, var0);
var10.provider = var8.getProvider();
var10.initCryptoPermission();
return var10;
} catch (Exception var11) {
var2 = var11;
}
} else {
var5 = var7.pad;
}
}
}
}
}
private static finalMap verificationResults =newIdentityHashMap();
static synchronized Exception getVerificationResult(Provider var0) {
Object var1 = verificationResults.get(var0);
if(var1 == PROVIDER_VERIFIED) {
return null;
} else if(var1 != null) {
return (Exception)var1;
} else if(verifyingProviders.get(var0) != null) {
return new NoSuchProviderException("Recursion during verification");
} else {
Exception var3;
try {
verifyingProviders.put(var0, Boolean.FALSE);
URL var2 = getCodeBase(var0.getClass());
verifyProviderJar(var2);
verificationResults.put(var0, PROVIDER_VERIFIED);
var3 = null;
return var3;
} catch (Exception var7) {
verificationResults.put(var0, var7);
var3 = var7;
} finally {
verifyingProviders.remove(var0);
}
return var3;
}
}
至此摆出,問(wèn)題的根本原因已經(jīng)找到了。
解決方法就是將BouncyCastlePrivate 設(shè)置一個(gè)靜態(tài)的夷野,而不是每次都new一個(gè)懊蒸。
private static BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
Cipher cipher = Cipher.getInstance("RSA",bouncyCastleProvider);
另外說(shuō)一點(diǎn),之所以每次向IdentityHashMap類(lèi)型verificationResults中put new BouncyCastleProvider會(huì)導(dǎo)致OOM是因?yàn)镮dentityHashMap比較key值是否相等對(duì)比的是引用即“==”而HashMap是p.hash== hash &&
((k = p.key) == key || (key !=null&& key.equals(k)))悯搔,至于為什么verificationResults是IdentityHashMap類(lèi)型的還要再看看源碼才能知道骑丸。
現(xiàn)在回想下第一個(gè)系統(tǒng)OOM很好理解,第二個(gè)系統(tǒng)之所以一段時(shí)間不重啟才會(huì)OOM就是因?yàn)関erificationResults本身是靜態(tài)的再加上應(yīng)用調(diào)用加密工具類(lèi)的次數(shù)不多妒貌,所以才會(huì)有這種現(xiàn)象通危,比較有趣!我之所以強(qiáng)調(diào)verificationResults是靜態(tài)的灌曙,因?yàn)橹挥惺庆o態(tài)對(duì)象才會(huì)出現(xiàn)這種系統(tǒng)運(yùn)行一段時(shí)間才會(huì)OOM的現(xiàn)象菊碟。后面我會(huì)再寫(xiě)一個(gè)內(nèi)存溢出的案例。
參考書(shū):
深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐
內(nèi)存dump分析工具:
Memory Analyzer (MAT)
參考文檔:
內(nèi)存快照排查OOM在刺,加密時(shí)錯(cuò)誤方法指定provider方式錯(cuò)誤引起的OOM【原創(chuàng)】
這篇文章寫(xiě)得詳細(xì)逆害,非常推薦头镊,可以說(shuō)我寫(xiě)的基本是照抄他的,只是為了加深下自己的印象魄幕。