內存快照排查OOM,加密時錯誤方法指定provider方式錯誤引發的OOM

 

寫在前面:java

最近開始總結內存方面的東西,已經總結之前遇到的一些內存案例分享下,接下來還有幾篇,而後是進程/線程相關的,逐漸造成個人知識體系樹緩存

若是你有興趣,能夠文章末尾的公衆號二維碼一塊兒梳理這些信息。安全

 

 

OOM問題通常都是人工代碼失誤, 多數其實在review階段應該能夠排除,本文主要是想記錄下內存快照排查OOM的一個過程 session

 

場景: 系統的交互安全徹底依賴各類加密作(作到了無session,徹底WEB無狀態,這個設計之後可講下),故加密變得很重要,但由於有新的加密引入了BouncyCastleProvider。故有修改,測試機一臺機器上線一段時間後,運行一段時間後,系統變得很是緩慢,併到最後出現了OOM,最終產生內存快照文件。jvm

 

分析:ide

JVM內存分析:觀察JVM內存,發現大量OU被使用沒法釋放引發頻發GC,致使系統緩慢(基本不可用)工具

內存快照分析OOM的緣由, 對於這種內存分析最好的方法是dump兩份,一份正常的,一份是發生OOM時候的進行對比測試

 

故障重現:加密

首先給1G內存給Tomcat,  當OOM時輸出日誌, JVM參數相關配置 以下:.net

-Xms1024m -Xmx1024m -Xmn512m -XX:PermSize=128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/dump/

 

正常啓動後,先dump份正常的用於後面對比分析(拿OOM後產生的快照和正常的快照進行對比能夠很方便找出泄漏的對象)

 

文件導入Eclipse Memory Analyzer

 

 

發現基本是Finalize佔用了內存

,填個坑,爲何會出現這麼多java.lang.ref.Finalizer佔用了內存呢

http://www.infoq.com/cn/articles/jvm-source-code-analysis-finalreference

http://knowledgebase.progress.com/articles/Article/000031452

 

 

而後開始壓測,2000次請求後,系統後面發現愈來愈慢,基本不可用了,查看GC,發現基本全部內存都滿了,一直在GC

 

繼續說快照, 此時再把內存dump下來導入,對比

 

打開快照,從全局圖查看已經很明顯能找到問題,內存都被JceSecurity佔用了861M,能夠肯定是JceSecurity的問題。

 

進一步分析,通常用到下面3個報表

 

1, 查看全局內存

2, 內存泄漏分析

3, 大對象排序

 

這裏直接用第2個,查看泄漏分析報告

 

 

96%的內存被JceSecurity這個類佔用了

 

發現問題對象後,查看Details

 

 

 顯示這個類的基本狀況,

 

是經過GC是ROOT追溯的最短路徑,這個能夠追溯到問題代碼的類樹的結構,並找到最終引用的代碼中

 

 

能夠最終找到直接引用類, 這裏最終存在類 javax.crypto.jceSecurity的變量, verificationResults裏面

 

2, 查看verificationResults裏的內容

 

說明:

with outgoning references  查看它引用了哪些內容,能夠當作是 集合包含了哪些內容

with incomming references 查看引用它的類

 

 

說明:

Shallow heap   是對象的自身佔用大小

Retained Heap 是對象包含內容的的總大小

 

發現內存基本被它裏面的一個BouncyCastleProvider佔用。

 

 

最後根據快照能得出 : 

致使問題的地方在,類javax.crypto.jceSecurity的變量verificationResults裏存的org.bouncycastle.jce.provider.BouncyCastleProvider

 

 

 

 

 

代碼尋找:

根據上面找到代碼中有用到org.bouncycastle.jce.provider.BouncyCastleProvider的地方,發現項目中的類CryptoUtil 

工具類提煉出來;

 

 

關鍵是這句,每次都new了個BouncyCastleProvider

Cipher cipher = Cipher.getInstance(Constants.CRYPOTO_NAME, new BouncyCastleProvider());

 

這個類,代碼來自7u40-b43 

 

從 cipher.init進去

 

 

這段用於驗證提供安全的provider對象是不是JCE能夠信任的JAR,並把緩存結果存起來,將把該種provider當key存入一個靜態的MAP中緩存起來, 故能夠理解一到OLD區也未回收.

 

verificationResults的驗證

 

 

 

 

值得說明的是,若是這裏是HashMap而不是IdentivHashMap的話也是不會內存泄漏的,爲何這樣說呢?

 

若是它這用的是HashMap, 不如new多少個BouncyCastleProvider, 在HashMap中也是一個,代碼證明下:

/**
 * 小測試,用於說明下Provider與IdentityHashMap的分別特殊處理
 * @author 包子(何錦彬) 2017.01.20
 *
 */
public class Test {
    public static void main(String[] args) {
        Map hashMap=new HashMap();
        hashMap.put( new BouncyCastleProvider(), "provoder1");
        hashMap.put( new BouncyCastleProvider(), "provoder2");
        System.out.println(hashMap.size()); //輸出是1
        
        Map identityMap=new IdentityHashMap();
        identityMap.put( new BouncyCastleProvider(), "provoder1");
        identityMap.put( new BouncyCastleProvider(), "provoder2");
        System.out.println(identityMap.size()); //輸出是2
        
    }
}

 

 

 緣由以下:

 

1, java.security.Provider的hashCode和equals方法是有作特殊處理的,在provider中,只要密鑰相等,兩個Provider的比較是相等的

 

2,IdentivHashMap這個類和HashMap的主要區別是,PUT時判斷兩個KEY是否相等用的是==

 

IdentivHashMap代碼以下:

 



而HashMap的這裏是:

 

 

HashMap 裏用的是 if (e.hash == hash && ((k = e.key) == key || key.equals(k))),比較的是hashCode與equals

 

解決

1.其實只要把指定方式由

Cipher cipher = Cipher.getInstance(Constants.CRYPOTO_NAME, new BouncyCastleProvider());

改爲

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

 

2, 把BouncyCastleProvider改爲單例模式

 

 

 

問1:

 

至於爲何要要指定用BouncyCastleProvider 

http://blog.csdn.net/defonds/article/details/42775183 

就OK,或者直接刪除不指定

 

 

問2:

 其實還能夠打印下存活對象, jmap -histo 7276 > dump.txt, 已經能看出些端倪

 

持續更新留言問題,解答疑問

 

歡迎關注個人公衆號,重現線上各類BUG, 一塊兒來構建咱們的知識體系

 

 

 或搜 「包子的實驗室」

相關文章
相關標籤/搜索