關於內存泄露與OOM的關系


內存泄漏達到一定程度會引發OOM
內存泄漏是指編寫的代碼中含有bug,不是指會引發程序執行結果錯誤那種,而是不可達的對象停留在堆中,即代碼中含有對象的強引用沒有沒釋放掉,導致該無用的對象無法被垃圾收集器收集。
假設內存足夠大,而內存泄漏的情況並不嚴重,只要還有足夠的空間分配給新的對象,那樣即使內存泄漏也不會引發OOM。

常見的幾種內存泄漏原因

靜態集合類引起的內存泄漏

靜態變量隨着類的加載被分配到方法區中,只要類還沒被卸載,靜態變量就不會被回收,而靜態的集合對象(指Collection對象,List、Set、Map),添加進去集合里的每個對象,集合都會持有其引用,只要我們沒有在代碼里顯示的remove。

HashSet中的對象屬性被修改后,再調用remove不起作用。

HashSet(內部封裝了HashMap)。首先我們要知道Map集合是通過鍵值對來儲存對象的。經過多次嘗試和查看源碼,發現HashSet儲存對象時是把add時候對象hashCode()返回的值和引用作為鍵值對儲存進去。
也就是說,如果我們想要修改對象屬性后仍然能用其引用將其從HashSet中移除(HashSet.remove(Object obj)),我們需要確保無論對象的屬性如何修改,它的hashCode()方法返回的值都始終如一。

監聽器

我們經常需要設置監聽器去監聽某個控件的狀態,需要留意我們在釋放控件的時候有沒有把對應的監聽器也釋放掉(監聽器持有對控件的引用)。

各種連接

比如數據庫連接(dataSourse.getConnection()),網絡連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關閉連接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 對象無法釋放,從而引起內存泄漏。這種情況下一般都會在try里面去的連接,在finally里面釋放連接。

外部模塊的調用

最直接的例子就是回調。比如我們在代碼中創建一個A類對象和一個B類對象,在A類對象中中調用B類對象的某個方法並需要B類對象進行回調時,A、B類對象會相互持有對方的引用,最后即使我們在代碼中釋放了對兩個對象的引用,GC也無法回收這兩個對象,導致內存泄漏。

單例模式

之前的文章里也稍微提到過,首先明確單例對象是靜態的,且是自己持有對自己的強引用,也就是說生命周期可以相當於應用進程的生命周期,運行時不會被GC回收,那么很顯然如果單例對象中持有對其他對象的強引用,那被引用的對象也是無法被回收的,特別是單例對象中含有對集合對象的引用的時候,要注意引用的釋放。

常見的內存溢出異常

Java堆溢出

java.lang.OutOfMemoryError: Java heap space

堆空間無法給新的對象分配內存空間且GC一次后仍然無法分配足夠的空間時會導致堆溢出。

棧溢出

java.lang.StackOverflowError

這是由於線程請求的棧深度大於虛擬機所允許的最大深度,無法壓入新的棧幀,這時可以檢查一下代碼中是否存在死循環的遞歸調用。

方法區溢出

java.lang.OutOfMemoryError: PermGen space

這里還可以分為運行時常量池溢出和方法區存放Class過多導致溢出。過去的話方法區的大小是設定好的,Java8之后將Class信息放到了元空間,並且元空間是可以動態申請擴展的,詳細的描述小伙伴可以自行搜索永久代到元空間的相關博客。

無法創建新進程

java.lang.OutOfMemoryError:unable to create natvie thread

就是棧空間不足了無法創建新的進程。

注:轉自簡書


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM