什麼是記憶體泄漏,為什麼會導致記憶體溢出?

  • 2020 年 7 月 10 日
  • 筆記

工作一段時間後,會經常聽到記憶體溢出,那記憶體溢出到底是哪裡的記憶體溢出,是什麼原因導致的,如何解決,今天就來深入了解一下.

在java中,要了解記憶體,需要先清楚jvm記憶體模型,我們常說的java記憶體實際上就是指Runtime Data Area,分為虛擬機棧、堆、方法區、程式計數器、本地方法棧五個部分.這裡不做具體介紹.

1.常見的記憶體泄露

(1)記憶體分配未成功,卻使用了它
(2)記憶體分配成功,但尚未初始化就引用它
(3)記憶體分配成功且初始化,但操作越過了記憶體的邊界
(4)忘記釋放記憶體,造成記憶體泄漏
(5)釋放了記憶體卻繼續使用它
以發生的方式來分類:
(1)常發性記憶體泄漏,發生記憶體泄漏的程式碼會被多次執行到,每次執行都會導致一塊記憶體泄漏
(2)偶發性記憶體泄漏
(3)一次性記憶體泄漏,發送泄漏的程式碼只會被執行一次
(4)隱式記憶體泄漏,程式在運行過程中不停地分配記憶體,但直到結束時才釋放記憶體。

2.為什麼會導致記憶體溢出

編寫java程式最為方便的地方就是我們不需要管理記憶體的分配和釋放,一切由jvm來進行處理,當java對象不再被應用時,等到堆記憶體不夠用時,jvm會進行垃圾回收,清除這些對象佔用的堆記憶體空間,如果對象一直被應用,jvm無法對其進行回收,創建新的對象時,無法從Heap中獲取足夠的記憶體分配給對象,這時候就會導致記憶體溢出。而出現記憶體泄露的地方,一般是不斷的往容器中存放對象,而容器沒有相應的大小限制或清除機制。容易導致記憶體溢出。

3.如何發現記憶體泄漏
可以直接使用VisualVM,已在JDK6.0 update 7 中自帶,能夠監控執行緒,記憶體情況,查看方法的CPU時間和記憶體中的對 象,已被GC的對象,反向查看分配的堆棧.

如果要在伺服器上使用Java VisualVM, 比如CentOS。那麼就出現 WARNING: environment variable DISPLAY is not set,因為一般伺服器都不會裝X server。我們可以在遠程機器上裝一個X server,比如windwos上,那麼就可以非常方便的查看伺服器運行情況。
如果有大量的FGC就要查詢是否有記憶體泄漏的問題了,圖中的FGC數量就比較大,並且執行時間較長,這樣就會導致系統的響應時間較長,如果對jvm的記憶體設置較大,那麼執行一次FGC的時間可能會更長。(直接運行linux上的jvisualvm,下載X-Manager,可以將視圖展現在本地機器上。)

從上圖可以發現執行FGC的情況,下午3:10分之前是沒有FGC的,之後出現大量的FGC。

上圖是jvm堆記憶體的使用情況,下午3:10分之前的記憶體回收還是比較合理,但是之後大量記憶體無法回收,最後導致記憶體越來越少,導致大量的full gc。

4.如何定位記憶體泄漏

1、查看Visual GC標籤,內容如下,這是輸出first的截圖
            
這是輸出forth的截圖:
通過2張圖對比發現:
老生代一直在gc,當程式繼續運行可以發現老生代gc還在繼續:
增加到了7次,但是老生代的記憶體並沒有減少。說明存在無法被回收的對象,可能是記憶體泄漏了。
如何分析是那個對象泄漏了呢?打開抽樣器標籤:點擊後如下圖:
按照程式輸出進行堆dump,當輸出second時,dump一次,當輸出forth時dump一次。
進入最後dump出來的堆標籤,點擊類:
點擊右上角:「與另一個堆存儲對比」。如圖選擇第一次導出的dump內容比較:
比較結果如下:
可以看出在兩次間隔時間內TestMemory對象實例一直在增加並且多了,說明該對象引用的方法可能存在記憶體泄漏。
如何查看對象引用關係呢?
右鍵選擇類TestMemory,選擇「在實例視圖中顯示」,如下所示:
左側是創建的實例總數,右側上部為該實例的結構,下面為引用說明,從圖中可以看出在類CyclicDependencies裡面被引用了,並且被HashMap引用。
如此可以確定泄漏的位置,進而根據實際情況進行分析解決。