加密工具類導致內(nèi)存溢出分析總結

這是第一次把問題分析的總結記錄下來,一是記錄下做備忘,二是把問題分析的過程和總結梳理下。

一共在兩個系統(tǒng)碰過因為加密導致OOM的問題:
第一次遇到這個問題的時候什么也不懂,只知道渾身發(fā)抖心亂跳……。不知道問題產(chǎn)生的原因更不知道該從何查起,運維同事給打了份dump日志,對我來說什么用都沒有。沒辦法只能請當時組里的牛人幫看。然后他就告訴我把一個變量設置成靜態(tài)的,修改后,發(fā)布到服務器上果然沒有再內(nèi)存飆升直至OOM了。當時也沒有請教下問題的根本原因是什么,只是問題解決就松了一口氣。
第二次是另外一個系統(tǒng),但是那個系統(tǒng)不像第一次碰到的系統(tǒng)那樣發(fā)布上去碰到訪問高峰就OOM。這個系統(tǒng)問題發(fā)現(xiàn)的比較有意思,為什么說有意思呢?因為問題一直都存在,只不過加密工具類調(diào)用的次數(shù)少,再加上這個系統(tǒng)發(fā)布比較頻繁,所以一直沒有OOM。直到有一次半個月沒有更新發(fā)布才報了OOM。

后來開始學習了解jvm,嘗試著去模擬重現(xiàn)當時的場景,然后分析系統(tǒng)OOM的原因。兩次問題的共同點都是多次調(diào)用加密類導致的。所以問題應該就在這個加密類。
模擬的代碼如下:
public static voidencrypt(){ try{ Cipher cipher = Cipher.getInstance("RSA", newBouncyCastleProvider()); // cipher.init(); }catch(NoSuchAlgorithmException e) { e.printStackTrace(); }catch(NoSuchPaddingException e) { e.printStackTrace(); } }
jvm參數(shù)設置:
-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/baitianxia/Documents/oom/heapdump.hprof
循環(huán)調(diào)用上面的方法就可以制造OOM了。
既然問題可以重現(xiàn),下面就可以開始分析問題的原因了:
jvm參數(shù)設置的是當OOM的時候打印heap dump到指定目錄。分析heap dump文件常用的工具是MAT(Memory Analyzer Tool)。
1)用MAT打開heapdump.hprof文件的截圖如圖1,看到占用內(nèi)存最大的是餅圖中的深藍色部分,點擊顯示JceSecurity類,那么可以初步斷定問題是由這個類導致的;

圖1

2)點擊Actions下的Dominator Tree可以查看占用內(nèi)存最大的對象,點擊后如圖2??梢钥吹接幸粋€IdentityHashMap存儲了大量的BouncyCastleProvider對象;


圖2

3)點擊JceSecurity-->List Objects-->with outgoing references顯示如圖3所示,可以看到是變量名為verificationResults的identityHashMap中存放了大量的BouncyCastleProvier,基本上就已經(jīng)找到導致問題的原因了;

圖3

4)查看源碼,可以看到因為verificationResults是靜態(tài)的,不會被GC,所以隨著加密工具類調(diào)用的次數(shù)增加,verificationResults存儲的BouncyCastle也越來越多,最終導致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;
        }
    }

至此,問題的根本原因已經(jīng)找到了。
解決方法就是將BouncyCastlePrivate 設置一個靜態(tài)的,而不是每次都new一個。

private static  BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
Cipher cipher = Cipher.getInstance("RSA",bouncyCastleProvider);

另外說一點,之所以每次向IdentityHashMap類型verificationResults中put new BouncyCastleProvider會導致OOM是因為IdentityHashMap比較key值是否相等對比的是引用即“==”而HashMap是p.hash== hash &&
((k = p.key) == key || (key !=null&& key.equals(k))),至于為什么verificationResults是IdentityHashMap類型的還要再看看源碼才能知道。
現(xiàn)在回想下第一個系統(tǒng)OOM很好理解,第二個系統(tǒng)之所以一段時間不重啟才會OOM就是因為verificationResults本身是靜態(tài)的再加上應用調(diào)用加密工具類的次數(shù)不多,所以才會有這種現(xiàn)象,比較有趣!我之所以強調(diào)verificationResults是靜態(tài)的,因為只有是靜態(tài)對象才會出現(xiàn)這種系統(tǒng)運行一段時間才會OOM的現(xiàn)象。后面我會再寫一個內(nèi)存溢出的案例。
參考書:
深入理解Java虛擬機:JVM高級特性與最佳實踐
內(nèi)存dump分析工具:
Memory Analyzer (MAT)
參考文檔:
內(nèi)存快照排查OOM,加密時錯誤方法指定provider方式錯誤引起的OOM【原創(chuàng)】
這篇文章寫得詳細,非常推薦,可以說我寫的基本是照抄他的,只是為了加深下自己的印象。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容