1. 程式人生 > >java 關於 Finalizer 過多導致記憶體(Res)緩慢上漲

java 關於 Finalizer 過多導致記憶體(Res)緩慢上漲

    病因: 事情的起因是由Flume的專案採集問題引發的. 測試人員發現用top命令檢視採集程序的Res一直不斷上漲姿勢. 所以懷疑是記憶體洩漏.


一, 對症下藥

    首先, 第一步肯定是先瞅瞅程式碼, 看看有沒有那些資源啥的沒關閉, 正如讀者所想 ---- 沒有發現.

二, 通過輔助工具

    最簡單檢視java記憶體的方法就是分析dump檔案. 

    1>  查詢當前程序的Pid , 如圖所示, pid 是 50480

    2>  到jdk安裝目錄bin下面找一個 jmap的命令

    3>  然後 ./jmap -dump:format=b,file=/opt/heap/heap1.bin 50480 , 得到 第一個 heap1.bin

    4>  過個把小時, 再使用這個命令  ./jmap -dump:format=b,file=/opt/heap/heap2.bin 50480 , 得到第二個heap2.bin

    5>  然後就是分析環節了, 我使用的是Eclipse的MAT外掛, 具體安裝過程百度之

    6>  用Eclipse 分別開啟heap檔案, 此時請看配圖



當我們把兩個, 都點選 Histogram 的時候, 會出現如下介面: 


根據圖中所示, 選擇對比兩個堆檔案變數的生成情況, 然後會得到下圖: 


快看快看, 就是這龜兒子導致了我們的記憶體一路飆升, 此時的你應該能想到哪些地方使用到了FileInputStream了. 對, 沒錯, 就去那個地方找. 如果你對程式碼不熟悉的話, 也可以參照下面方法來定位位置:


(1) 如圖, 我們再次點選這個, 然後得到變數生成表單.


(2) 在物件表單中尋找到我們剛剛看到的龜兒子FiliInputStream, 然後右擊選擇List Objects, 然後選擇 outGoing...


(3) 然後你就可以看到這個FileInputStream到底是什麼了, 這時候去程式碼那邊找原因.

7> 由於涉及到程式碼保密協議, 我就用測試程式碼替代原始碼, 大致邏輯如下


    表面上看, 這段程式碼沒什麼大問題, 因為我的reader是需要在其他地方使用, 所以會在使用完之後關閉這個BufferedReader流. 然後巢狀的流也會相應的關閉. 沒毛病啊.

    也許這是所有程式猿公認的, BufferedReader 流關閉, 巢狀的流也會相應的關閉. 這句話沒有錯. 但是, 在高速度和高併發的情況下, 對於流的關閉就會有問題.

    因為 如果沒有顯式關閉流, jvm會有一個finalize()的方法來做最後的防線, 也就是說我們BufferedReader流雖然關閉了, 但是巢狀的流不關閉的話, 只能通過finalize()方法來關閉. 

    但是在高併發情況下, 也許FileInputStream流開的很多, 但是finalize() 是單執行緒操作的(具體內部原理請百度之).finalize()方法會把需要釋放的資源放到一個Queue中, 如果釋放的動作慢於產生的速度, 這時就會有大量的Finalizer堆積, 導致記憶體的異常.

    所以, 建議在高併發, 高速度的條件下, 儘量別使用巢狀的BufferedReader流. 改用下面:


    通過反覆測試, 沒有出現Finalizer 大量堆積的情況, 異常解除.

由於本人是菜鳥一枚, 文中不免會出現這樣那樣的錯誤, 望大佬們提出寶貴建議, 十分感謝.