The NoteBook of EricKong

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            611 Posts :: 1 Stories :: 190 Comments :: 0 Trackbacks

          引言

          Java 開發者一般不需要考慮內存釋放問題,全交由 GC 去處理。但是在一些生產環境中,JVM 經過長時間運行后,即使是一些很小的未釋放的 Java 對象,日積月累也會導致內存資源枯竭,最終使 Java 應用崩潰的問題。本文將就一個 AIX 平臺上基于 IBM JDK 開發的 Java 應用內存枯竭的實際案例分析過程,來引領讀者理解基于 IBM JDK 的 Java 應用內存泄漏調查方法,以及分析思路。

          第一步,判斷是否是內存泄漏問題

          根據生產環境出現的錯誤日志以及 GC 日志文件,進行初步判斷是否是內存泄露問題。

          Java 應用的錯誤日志:

          “***WARNING*** Java heap is almost exhausted: 4% free Java heap
           


          應用程序中對可用內存做了判斷,當可用內存比較低的時候輸出了 WARNING 的日志。

          使用 IBM pattern modeling and Analysis Tools for Java Garbage Collector 來分析 GC 日志。

          圖 1. 選擇打開 IBM JDK 的 GC 日志文件


          圖 2. 點擊 Graph View Part 顯示


          圖 3. 顯示 GC 分析圖

          從圖中可以看出 Java 內存的堆 (Heap) 的使用情況是持續的上升趨勢。

          由此我們可以得出結論,Java 應用程序存在內存泄漏問題,導致內存堆得不到釋放。

          第二步,截取 Java 內存堆的轉存儲文件

          在得出是內存堆泄漏的問題結論后,接下來就需要取得內存堆的轉存儲文件來做進一步分析。

          在 AIX 平臺上截取 IBM JDK 的內存堆的轉存儲文件前,需要先對 IBM JDK 的 JVM 參數進行設置。有 2 種設置方式:

             設置 IBM JDK 的全局變量:

              export IBM_HEAPDUMP=true


             添加 JVM 啟動參數:

             -Xdump:system+heap+java:events=user,request=exclusive+prepwalk+compact

             設定完后需要重啟 JVM, 使設定生效。然后可以在 kill -QUIT pid 命令來生成轉存儲文件 (Dump),pid 為實際啟動的 JVM 進程 ID。

             當內存泄漏情況非常小且緩慢的時候,無法從 1 個或 2 個轉存儲文件中分析出導致泄漏的 Java 對象。根據上面 GC 的日志趨勢,制定如下的轉存儲文件的截取的方案。
                 截取周期為 1 星期以上,每天一次。
                 每天固定時間截取,且避開發生大的 GC 的時間段。

             這樣可以得到幾個可以用來比對分析的轉存儲文件,以及避免正在運行中得一些 Java 對象對于分析的干擾。

          第三步,分析轉存儲文件

          使用 MAT (Memory Analyzer Tool) 工具來分析轉存儲文件。由于實際轉存儲文件非常大,需要調整 MAT 工具的啟動參數文件(MemoryAnalyzer.ini),32 位的 window 平臺的話,最大也只能設定到 1.5G。因此當分析超大的轉存儲文件時,建議在 64 位 window 平臺上做,這樣可以分配更多的內存給 MAT 工具使用。

          1)查找可疑泄漏點

           在 MAT 的 Overview 中,可以點擊”Leak Suspect”來生成 Leak Suspect Reports, 做最直觀的分析。


          圖 4. 點擊 Leak Suspect


          圖 5. 顯示某 1 天的轉存儲文件分析結果。

          如果連續幾天的轉存儲文件中,都是這個 Suspect 實例 (Instance) 的所占比例最大,且所占內存空間也在不斷上升,沒有下降的趨勢的話,那基本上可以斷定該實例是發生泄漏的對象了。

          點擊打開該 Suspect 的 Detail 信息。


          圖 6. 點擊 Details 鏈接

          通過比對連續幾天的轉存儲文件,可以發現是 Hashtable 中得 Entry 對象的占用空間不斷變大。


          圖 7. 顯示 Detail 信息



          那接下來進一步深入分析,到底在 Hashtable 中占用空間增大到底是什么實例。

          2)深入分析

          點擊 Suspect 實例,打開該實例的 Dominator Tree。


          圖 8. 選擇 Dominator Tree 選項

          可以在 Dominator Tree 中看到 Hashtable 中放的 Java Instance,依次為

          Company[]  -> Event[] -> Task (Manager, Handler, xxxxx)


          圖 9. 顯示 Dominator Tree 信息

          分析其中 1 個復雜的 Task,點擊 Path to GC Roots 繼續深入分析 Task 的引用關系。Weak 和 Soft 引用會在 Major GC 是被釋放,所以查看下不包含他們的引用關系。


          圖 10. 顯示可疑點的引用關系圖

          根據 Java 應用的代碼調查,Company 和 Event 是常駐于 Service 靜態實例中。

          引用 A 代碼分析

          引用 A 的順序 Task <- Thread <- Record.Hashtable。Record 中得 Hashtable 中有對一個 Thread 的引用是比較奇怪的。因為那將導致這個 Thread 的實例沒法釋放,從而導致 Task 的實例沒釋放。查看 Java 應用代碼發現,Thread 的實例被放入 Record 實例的靜態 Hashtable 中,但是沒有調用 Remove。


          清單 1

          雙擊代碼全選
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          public class XXXXXX extends XXXXXBase 
           // …
            private static Hashtable currentXXXXXXX = new Hashtable(); 
           // …
            public void process (xxxx){ 
            // …
            currentXXXXXX.put(Thread.currentThread(), XXXX_); 
            // …
           }

          引用 B 代碼分析

          和引用 A 相似,Thread 被放入了 Factory 的靜態實例的 Hashtable 中,而且沒有 Remove。

          引用 C 代碼分析

          Task 是經由 Event 每次新建實例來啟動執行,當執行完后應當銷毀該 Task 的實例,不應長期存在于內存中。上圖的應用分析顯示 Event 中引用了 Task 的實例,因此 Task 沒法釋放。查看 Event 的代碼證明了確認如此,沒有將新建的 Task 實例重設為 Null。


          圖 11 引用分析結構圖

          直接用 OQL(Object Query Language) 來查詢該 Task 實例,可以看到該 Task 的實例隨著時間不但增多。


          圖 12. OQL 查詢結果

          綜上所述,由于強引用的關系存在于靜態實例中,所以 Task 的實例沒法釋放,最終導致了內存枯竭。Java 內存堆泄漏的問題,多發生在靜態 Hashtable、Hashmap、Vector 的使用不當,還有諸如打開文件后沒有關閉,DB 和 Socket 連接打開沒有關閉之類的都會導致 GC 無法釋放引用的 Java 實例。

          本文中所描述的通過 Java 內存堆和 GC 日志來分析內存泄漏方法,以及 Eclipse MAT 和 IBM Pattern Modeling and Analysis Tool for Java Garbage Collector 工具適用于調查任何平臺上的 Java 應用程序。但文中提及的截取 Java 內存堆的轉存儲文件方法只限于在 AIX 平臺上的 IBM JDK。針對 Linux, Window 等平臺,或 Sun JDK 等有專門的截取方法,不在本文中一一描述。

          結束語

          本文通過對一個實際內存泄漏的分析,以及一些實際使用中的工具和經驗技巧的介紹,展示里分析 Java 內存分析的常規方法。

          posted on 2014-05-13 19:08 Eric_jiang 閱讀(295) 評論(0)  編輯  收藏 所屬分類: WebShpere
          主站蜘蛛池模板: 石城县| 漳浦县| 施甸县| 马山县| 曲水县| 宁城县| 通道| 门头沟区| 高要市| 甘肃省| 加查县| 观塘区| 博野县| 建水县| 巴马| 东城区| 黄大仙区| 尼木县| 钟祥市| 湘乡市| 无棣县| 宁河县| 东兴市| 永吉县| 桓仁| 平阴县| 佛坪县| 灌南县| 天水市| 汝州市| 中西区| 永嘉县| 县级市| 伊宁市| 句容市| 文成县| 安庆市| 彰武县| 始兴县| 台前县| 龙海市|