記憶體流失定義:一個不再被程式使用的對象或變數還在記憶體中佔有儲存空間。
由於java的JVM引入了記憶體回收機制,記憶體回收行程會自動回收不再使用的對象,瞭解JVM回收機制的都知道JVM是使用引用計數法和可達性分析演算法來判斷對象是否是不再使用的對象,本質都是判斷一個對象是否還被引用。那麼對於這種情況下,由於代碼的實現不同就會出現很多種記憶體流失問題(讓JVM誤以為此對象還在引用中,無法回收,造成記憶體流失)。
1、靜態集合類,如HashMap、LinkedList等等。如果這些容器為靜態,那麼它們的生命週期與程式一致,則容器中的對象在程式結束之前將不能被釋放,從而造成記憶體流失。簡單而言,長生命週期的對象持有短生命週期對象的引用,儘管短生命週期的對象不再使用,但是因為長生命週期對象持有它的引用而導致不能被回收。
2、各種串連,如資料庫連接、網路連接和IO串連等。在對資料庫進行操作的過程中,首先需要建立與資料庫的串連,當不再使用時,需要調用close方法來釋放與資料庫的串連。只有串連被關閉後,記憶體回收行程才會回收對應的對象。否則,如果在訪問資料庫的過程中,對Connection、Statement或ResultSet不顯性地關閉,將會造成大量的對象無法被回收,從而引起記憶體流失。
3、變數不合理的範圍。一般而言,一個變數的定義的作用範圍大於其使用範圍,很有可能會造成記憶體流失。另一方面,如果沒有及時地把對象設定為null,很有可能導致記憶體流失的發生。
public class UsingRandom {private String msg;public void receiveMsg(){readFromNet();// 從網路中接受資料儲存到msg中saveDB();// 把msg儲存到資料庫中}}
如上面這個虛擬碼,通過readFromNet方法把接受的訊息儲存在變數msg中,然後調用saveDB方法把msg的內容儲存到資料庫中,此時msg已經就沒用了,由於msg的生命週期與對象的生命週期相同,此時msg還不能回收,因此造成了記憶體流失。
實際上這個msg變數可以放在receiveMsg方法內部,當方法使用完,那麼msg的生命週期也就結束,此時就可以回收了。還有一種方法,在使用完msg後,把msg設定為null,這樣記憶體回收行程也會回收msg的記憶體空間。
4、內部類持有外部類,如果一個外部類的執行個體對象的方法返回了一個內部類的執行個體對象,這個內部類對象被長期引用了,即使那個外部類執行個體對象不再被使用,但由於內部類持有外部類的執行個體對象,這個外部類對象將不會被記憶體回收,這也會造成記憶體泄露。
5、改變雜湊值,當一個對象被儲存進HashSet集合中以後,就不能修改這個對象中的那些參與計算雜湊值的欄位了,否則,對象修改後的雜湊值與最初儲存進HashSet集合中時的雜湊值就不同了,在這種情況下,即使在contains方法使用該對象的當前引用作為的參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從HashSet集合中單獨刪除當前對象,造成記憶體泄露