內存泄漏定義(memory leak):一個再也不被程序使用的對象或變量還在內存中佔有存儲空間。java
一次內存泄漏彷佛不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。
內存溢出 out of memory :指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,可是你卻存儲long類型的數據,那麼結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出。
兩者的關係:算法
因爲java的JVM引入了垃圾回收機制,垃圾回收器會自動回收再也不使用的對象,瞭解JVM回收機制的都知道JVM是使用引用計數法和可達性分析算法來判斷對象是不是再也不使用的對象,本質都是判斷一個對象是否還被引用。那麼對於這種狀況下,因爲代碼的實現不一樣就會出現不少種內存泄漏問題(讓JVM誤覺得此對象還在引用中,沒法回收,形成內存泄漏)。數據庫
一、靜態集合類,如HashMap、LinkedList等等。若是這些容器爲靜態的,那麼它們的生命週期與程序一致,則容器中的對象在程序結束以前將不能被釋放,從而形成內存泄漏。簡單而言,長生命週期的對象持有短生命週期對象的引用,儘管短生命週期的對象再也不使用,可是由於長生命週期對象持有它的引用而致使不能被回收。網絡
二、各類鏈接,如數據庫鏈接、網絡鏈接和IO鏈接等。在對數據庫進行操做的過程當中,首先須要創建與數據庫的鏈接,當再也不使用時,須要調用close方法來釋放與數據庫的鏈接。只有鏈接被關閉後,垃圾回收器纔會回收對應的對象。不然,若是在訪問數據庫的過程當中,對Connection、Statement或ResultSet不顯性地關閉,將會形成大量的對象沒法被回收,從而引發內存泄漏。this
三、變量不合理的做用域。通常而言,一個變量的定義的做用範圍大於其使用範圍,頗有可能會形成內存泄漏。另外一方面,若是沒有及時地把對象設置爲null,頗有可能致使內存泄漏的發生。spa
1 public class Memorytest 2 { 3 private String msg; 4 5 public void reciveMsg() throws UnsupportedEncodingException{ 6 readFromNet(); 7 saveDB(); 8 } 9 10 // 從網絡從接受數據保存到msg中 11 private void readFromNet() throws UnsupportedEncodingException 12 { 13 byte [] data = new byte[128]; 14 this.msg = new String(data , "utf-8"); 15 } 16 17 // 把msg持久化到數據庫中 18 private void saveDB() 19 { 20 // update(msg); 21 } 22 }
如上面這個僞代碼,經過readFromNet方法把接受的消息保存在變量msg中,而後調用saveDB方法把msg的內容保存到數據庫中,此時msg已經就沒用了,因爲msg的生命週期與對象的生命週期相同,此時msg還不能回收,所以形成了內存泄漏。code
實際上這個msg變量能夠放在receiveMsg方法內部,當方法使用完,那麼msg的生命週期也就結束,此時就能夠回收了。還有一種方法,在使用完msg後,把msg設置爲null,這樣垃圾回收器也會回收msg的內存空間。對象
四、內部類持有外部類,若是一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即便那個外部類實例對象再也不被使用,但因爲內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會形成內存泄露。blog
五、改變哈希值,當一個對象被存儲進HashSet集合中之後,就不能修改這個對象中的那些參與計算哈希值的字段了,不然,對象修改後的哈希值與最初存儲進HashSet集合中時的哈希值就不一樣了,在這種狀況下,即便在contains方法使用該對象的當前引用做爲的參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會致使沒法從HashSet集合中單獨刪除當前對象,形成內存泄露。生命週期
六、單例模式
不正確使用單例模式是引發內存泄漏的一個常見問題,單例對象在初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部的引用,那麼這個對象將不能被JVM正常回收,致使內存泄漏。