Java垃圾回收總結

 

一、在理解垃圾回收前需要了解一個對象在堆內存中的引用狀態,分爲三種:

1.可達狀態:對象創建後,有一個或以上的引用變量引用它。

2.可恢復狀態:程序中某個對象不再有任何引用變量引用它,但是還是有可能重新被其他引用變量引用。

3.不可達狀態:對象與所用引用變量的引用都被切斷,且jvm執行finalize()方法進行資源清理後沒有成爲可達狀態,那麼這個對象將永久的失去引用,編程不可達狀態。(GC真正回收的正是處於這種狀態的對象)。

 

public void test(){
     //此處對象爲可達狀態
     String a=new String("this is a string object");
     //此處a引用只想了另一個變量,則上面的string對象成爲可恢復狀態
     a=new String("another string object");
}

 

 

二、垃圾回收有如下機制:

1.垃圾回收只負責回收堆內存中的對象,不會回收任何物理資源(如:數據庫連接,網絡IO等)。

2.程序無法精確控制垃圾回收的運行,垃圾回收只有再合適的時候進行。對象成爲不可達狀態後,JVM會再合適的時候回收該對象。

3.垃圾回收任何對象前之前總會調用這個對象的finalize方法,進行資源清理。finalize方法可能使該對象重新成爲可達狀態,從而使使JVM取消對它的回收。

 

 

三、強制垃圾回收:

強制垃圾回收其實是建議JVM立即進行GC,但JVM是否進行gc操作是不確定的,垃圾回收機制只是在接收到通知儘快進行垃圾回收。強制垃圾回收有如下兩種方式,

System.gc();

Runtime.getRuntime().gc();

 

 

四、理解finalize方法:

系統在垃圾回收前總會調用finalize方法進行資源清理,這個操作和垃圾回收操作是一起發生但要先於GC。只有程序任務需要額外的內存時纔會進行GC。finalize有如下特點:

1.不要主動調用finalize方法,該方法應交給垃圾回收機制調用。

2.finalize方法何時執行無法確定。

3.JVM執行finalize方法時,有可能將對象從可恢復狀態變成可達狀態。

4.JVM執行finalize方法時,垃圾回收機制不會報錯,程序繼續執行。

如下例子說明垃圾回收的不確定性:

public class FinalizeTest {
	private static FinalizeTest ft=null;
	public void info(){
		System.out.println("調用系統的finalize方法");
	}
	
	public static void main(String[] args) throws InterruptedException {
		
		//創建對象語句也會進行垃圾回收
		new FinalizeTest();
		
		//通知系統立即進行垃圾回收
		System.gc();//1
		
		
//		Thread.sleep(2000);//3
		
		ft.info();
		
	}
	//重寫了finalize方法
	public void finalize(){
		ft=this;
	}
}

 上述例子中註釋掉3處代碼可以發現程序報空指針異常,取消註釋後,程序打印“ 調用系統的finalize方法”,說明程序顯示執行垃圾回收,但finalize方法不會立即執行,當延遲2s後發現垃圾回收執行了。如果在1和3處代碼中間加入下列代碼則可強制調用finalize方法。

 

//強制垃圾回收調用可恢復對象的finalize方法,執行此方法前一定先通知系統進行垃圾回收操作
//		Runtime.getRuntime().runFinalization();
		System.runFinalization();//2

 

五、是否應該在程序中顯示調用垃圾回收?

首先你需要知道兩個概念:

JVM的gc有minor GC和major GC(也就是大家說的Full GC),JVM會很頻繁的做minor GC,如果內存塊佔滿的話,JVM會做Full GC,Full GC是對整個JVM內存堆做GC,所示耗時比minor GC要長很多。System.gc()最終執行的是Full GC
1,服務器老是崩潰是因爲內存很快被佔滿,在允許的條件下你可以適當加大內存配置,用mx和ms控制內存堆大小
-Xms
-Xmx
2,如果你的程序需要通過System.gc()來減少JVM崩潰的機率,那麼你的程序10有8,9存在問題,需要從你程序本身去進行優化
3,可以把GC配置爲CMS回收方式,以提高回收效率
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseParNewGC
-XX:CMSFullGCsBeforeCompaction=0
-XX:CMSInitiatingOccupancyFraction=75
-XX:+CMSParallelRemarkEnabled
-XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled
4,-XX:+PrintGCTimeStamps  -XX:+PrintGCDetails -XX:+PrintHeapAtGC用這些參數輸出GC日誌,然後分析,

 

通過上述描述可知道,System.gc()最終執行的是Full GC,執行時會對整個JVM堆內存做GC,這將給程序帶來一個嚴重的性能損失。一般不建議這麼做。

 

六:爲防止內存泄露,對象引用用完後都清空?

清空對象引用應該是一種例外,而不是一種規範行爲,這樣做會使代碼很亂,一般而言只要類是自己管理內存,程序員就應該警惕內存泄露問題。

要防止內存泄露,下面是一些快速上手的實用技巧:
1. 當心集合類,比如 HashMap,ArrayList等,因爲這是最容易發生內存泄露的地方.當集合對象被聲明爲static時,他們的生命週期一般和整個應用程序一樣長。
2. 注意事件監聽和回調.當註冊的監聽器不再使用以後,如果沒有被註銷,那麼很可能會發生內存泄露.
3. "當一個類自己管理其內存空間時,程序員應該注意內存泄露." 常常是一個對象的成員變量需要被置爲null 時仍然指向其他對象。

下面舉一個模擬棧的例子:

public class Stack {
	private Object[] elements;
	private int size=0;
	private static int CAPACITY=16;
	public Stack(){
		elements=new Object[CAPACITY];
	}
	
	public void push(Object e){
		ensureCapacity();
		elements[size++]=e;
	}
	
	public Object pop(){
		if(size==0)
			throw new EmptyStackException();
		return elements[--size];
	}
		
	private void ensureCapacity(){
		if(elements.length==size){
			elements=Arrays.copyOf(elements, 2*size+1);
		}
	}
	
}

 此例子會有一個內存泄露的現象,從棧中彈出來的對象不會當做垃圾回收,即使棧不再引用這些對象,這是因爲,棧內部還維護者這些彈出對象的過期引用。所謂過期引用是指永遠不會被解除的引用。隨着時間推移,無法回收對象越來越多,程序性能越來越差最後可能導致內存溢出。此時可以做如下處理:

 

       public Object pop2(){
		if(size==0)
			throw new EmptyStackException();
		Object result= elements[--size];
		elements[size]=null;
		return result;
	}

一旦數組元素變成非活動的一部分,就手動清空這些元素。

 

七、對象的軟、弱和虛引用,

正常程序中的引用變量爲強引用。當程序中需要避免在執行期間將對象留在內存中,可以使用如下幾種引用:

1.軟引用:當系統內存空間不足時,會回收它,但空間足夠時,不會回收,程序也可以使用改對象。

2.弱引用:跟軟引用類似,比軟引用級別耕地,當GC操作時,不管內存是否足夠,對象總會回收。

3.虛引用:和沒有引用效果大致相同,主要用於跟蹤對象垃圾回收狀態,不能單獨使用,必須和引用隊列(ReferenceQueue)聯合使用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章