Java內存泄漏

Java中的內存管理

要了解Java中的內存泄漏,首先就得知道Java中的內存是如何管理的。java

在Java程序中,咱們一般使用 new 爲對象分配內存,而這些內存空間都在堆上。程序員

Java判斷對象是否能夠回收使用的而是可達性分析算法。算法

這個算法的基本思路就是經過一系列名爲 "GC Roots" 的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是不可用的,下圖對象 object5, object6, object7 雖然有互相判斷,但它們到 GC Roots 是不可達的,因此它們將會斷定爲是可回收對象。數據庫

在 Java 語言中,可做爲 GC Roots 對象的包括以下幾種:編程

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧(Native方法)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象

什麼是Java中的內存泄漏

Java 中的內存泄漏,廣義並通俗的說,就是:再也不會被使用的對象的內存不能被回收,就是內存泄漏。緩存

Java 中的內存泄漏與 C++ 中的表現有所不一樣。網絡

在 C++ 中,全部被分配了內存的對象,再也不使用以後,都必須程序員手動的去釋放他們。可是在 Java 中,咱們不用本身釋放內存,無用的內存由 GC 自動清理,這也極大的簡化了咱們的編程工做。但實際有時候一些再也不會被使用的對象在 GC 看來不能被釋放就會形成內存泄漏。dom

對象都是有生命週期的,有的長,有的短。若是長生命週期的對象持有短生命週期的引用,就極可能會出現內存泄漏。例如:jvm

public class Test {
	Object object;
	public void method() {
		object = new Object();
		// ...
	}
}

這裏的 object 實例,其實咱們指望它只做用於 method() 方法中,且其餘地方也不會再用到它,可是當 method() 方法執行完以後,object對象所分配的內存不會立刻被認爲是能夠被釋放的對象。只有在 Test 類建立的對象被釋放後纔會被釋放。嚴格地說,這就是一種內存泄漏。解決辦法就是將 object 做爲 method() 方法中的局部變量。固然也能夠在使用完 object 以後 將其置爲 null。性能

public class Test {
	Object object;
	public void method() {
		object = new Object();
		// ...
		object = null;
	}
}

這樣,以前 new Object() 分配的內存就能夠被 GC 回收。

Java中內存泄漏的例子

  1. 靜態集合類

如HashMap、LinkedList等等。若是這些容器爲靜態的,那麼它們的生命週期與程序一致,則容器中的對象在程序結束以前將不能被釋放,從而形成內存泄漏。簡單而言,長生命週期的對象持有短生命週期對象的引用,儘管短生命週期的對象再也不使用,可是由於長生命週期對象持有它的引用而致使不能被回收。

static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 
{ 
    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

在這個例子中,代碼棧中存在 Vector 對象的引用 v 和 Object 對象的引用 o 。在 For 循環,咱們不斷的生成新的對象,而後將其添加到 Vector 對象中,以後將 o 引用置空。問題是當 o 引用被置空後,若是發生 GC,咱們建立的 Object 對象是否可以被 GC 回收呢?答案是否認的。由於, GC 在跟蹤代碼棧中的引用時,會發現 v 引用,而繼續往下跟蹤,就會發現 v 引用指向的內存空間中又存在指向 Object 對象的引用。也就是說盡管o 引用已經被置空,可是 Object 對象仍然存在其餘的引用,是能夠被訪問到的,因此 GC 沒法將其釋放掉。若是在此循環以後, Object 對象對程序已經沒有任何做用,那麼咱們就認爲此 Java 程序發生了內存泄漏。

  1. 各類鏈接,如數據庫鏈接、網絡鏈接和IO鏈接等

在對數據庫進行操做的過程當中,首先須要創建與數據庫的鏈接,當再也不使用時,須要調用close方法來釋放與數據庫的鏈接。只有鏈接被關閉後,垃圾回收器纔會回收對應的對象。不然,若是在訪問數據庫的過程當中,對Connection、Statement或ResultSet不顯性地關閉,將會形成大量的對象沒法被回收,從而引發內存泄漏。

  1. 變量不合理的做用域

通常而言,一個變量的定義的做用範圍大於其使用範圍,頗有可能會形成內存泄漏。另外一方面,若是沒有及時地把對象設置爲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 的內存空間。

  1. 內部類持有外部類

若是一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即便那個外部類實例對象再也不被使用,但因爲內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會形成內存泄露。

  1. 改變哈希值

當一個對象被存儲進 HashSet 集合中之後,就不能修改這個對象中的那些參與計算哈希值的字段了,不然,對象修改後的哈希值與最初存儲進 HashSet 集合中時的哈希值就不一樣了,在這種狀況下,即便在 contains 方法使用該對象的當前引用做爲的參數去 HashSet 集合中檢索對象,也將返回找不到對象的結果,這也會致使沒法從 HashSet 集合中單獨刪除當前對象,形成內存泄露。

public static void main(String[] args) 
{ 
    Set<Person> set = new HashSet<Person>(); 
    Person p1 = new Person("唐僧","pwd1",25); 
    Person p2 = new Person("孫悟空","pwd2",26); 
    Person p3 = new Person("豬八戒","pwd3",27); 
    set.add(p1); 
    set.add(p2); 
    set.add(p3); 
    System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素! 
    p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變 
    set.remove(p3); //此時remove不掉,形成內存泄漏
    set.add(p3); //從新添加,竟然添加成功 
    System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素! 
    for (Person person : set) 
    { 
        System.out.println(person); 
    } 
}
  1. 單例對象在被初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,致使內存泄露
  2. 緩存泄漏

內存泄漏的另外一個常見來源是緩存,一旦你把對象引用放入到緩存中,他就很容易遺忘,對於這個問題,可使用 WeakHashMap 表明緩存,此種 Map 的特色是,當除了自身有對 key 的引用外,此 key 沒有其餘引用那麼此 map 會自動丟棄此值

  1. 監聽器和回調

內存泄漏第三個常見來源是監聽器和其餘回調,若是客戶端在你實現的 API 中註冊回調,卻沒有顯示的取消,那麼就會積聚。須要確保回調當即被看成垃圾回收的最佳方法是隻保存他的弱引用,例如將他們保存成爲 WeakHashMap 中的鍵。

內存泄露解決的原則

1.儘可能減小使用靜態變量,類的靜態變量的生命週期和類同步的。

2.聲明對象引用以前,明確內存對象的有效做用域,儘可能減少對象的做用域,將類的成員變量改寫爲方法內的局部變量;

3.減小長生命週期的對象持有短生命週期的引用;

4.使用StringBuilder和StringBuffer進行字符串鏈接,Sting和StringBuilder以及StringBuffer等均可以表明字符串,其中String字符串表明的是不可變的字符串,後二者表示可變的字符串。若是使用多個String對象進行字符串鏈接運算,在運行時可能產生大量臨時字符串,這些字符串會保存在內存中從而致使程序性能降低。

5.對於不須要使用的對象手動設置null值,無論GC什麼時候會開始清理,咱們都應及時的將無用的對象標記爲可被清理的對象;

6.各類鏈接(數據庫鏈接,網絡鏈接,IO鏈接)操做,務必顯示調用close關閉。

相關文章
相關標籤/搜索