垃圾收集器與內存分配策略(讀後感三:回收內存)

在前兩篇博客中,我們有學習到在Java虛擬機中,內存的分配以及創建對象的過程

我們已經知道,程序計數器,虛擬機棧,本地方法棧都是線程私有的,隨着線程的生而生,隨着線程的死而消亡,當一個方法或者線程結束時,內存就自然的被回收了。但是在Java堆和方法區中,只有在程序運行時我們才知道要創建哪些對象,所以這部分的內存的分配和回收都是動態的,所以,垃圾收集器所關注的就是部分內存,回收的就是這部分內存。

所以現在存在我們腦海裏的疑惑就是:垃圾收集器(GC)如何回收內存?什麼時候回收?當然,我們還需要知道哪些內存需要被回收。

一:哪些內存需要被回收?(即如何判斷對象已死)

1. 引用計數算法

2 .可達性分析算法

3. 關於引用

4. 最終判斷對象是生存還是死亡

引用計數算法: 即 給對象一個引用計數器,每當一個地方引用他,計數器就+1,當引用失效時,計數器值就減1;任何時刻當計數器爲0的對象就是不可能再被使用的,就被回收掉。

但是這種算法很難解決對象之間相互循環引用的問題,這樣對象的計數器不爲一,但也有永不再被使用的可能。

可達性分析算法:基本思路就是 通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何 引用鏈相連(不可達)時,則證明此對象是不可用的。

如下圖所示:

1234

其中,object5分支下面的object6,object7以及object5雖然互相有關聯,但是他們到GC Roots不可達,所以也會將他們被判定爲是可回收的對象,但是如果用引用計數算法,則object5就不會被判定爲可回收的對象。

那麼,可以被用來當做GC Roots的對象有哪些?

A. 虛擬機棧(棧幀中的本地變量表)中引用的對象;

B. 方法區中類靜態屬性引用的對象

C. 方法區中常量引用對象;

D. 本地方法棧中JNI(即一般說的Native方法)引用的對象

 

關於引用:無論是通過引用計數算法還是可達性分析算法,判斷對象的死活都與“引用有關”。

我們需要達到這樣一種需求:當內存夠用時,一些對象可不被回收,當我們的內存空間不夠的時候,則自動回收掉一些對象。

所以我們把引用分爲以下幾種類型:

A. 強引用:不管內存夠不夠,只要強引用還存在,就永遠不會被回收:類似於'Object obj = new Object();'

B. 軟引用:當內存空間足夠時,不會被回收,但是不夠了,在內存溢出發生之前,會對這部分對象 二次回收,如果二次回收內存還不夠,纔會拋出內存溢出異常

C. 弱引用:無論當前內存空間是否足夠,都會回收掉只被弱引用關聯的對象

D. 虛引用:虛引用的作用是:當一個對象被垃圾收集器回收的時候,我們可收到一個系統通知。

對象究竟生存還是死亡:即使在可達性分析算法中不可達的對象,也並非是“非死不可”,因爲每個對象都有一次“自我拯救”的機會,因爲每個對象要被回收,至少要經歷兩次標記。第一次,如果在可達性分析算法中不可達,那麼他被標記一次,進行“牢籠”中,並且進行“審問”,審問的問題是是否其finalize()方法被虛擬機調用過或者已被覆蓋,如果是,則視爲沒必要執行,就進行了第二次標記,如果沒有,那麼這個“嫌犯”就被放入一個F-Queue隊列中,由虛擬機自動創建的一個優先級較低的Finalizer線程執行它,如果這個對象在finalize()方法中執行緩慢,或者發生了死循環,則會導致F-Queue隊列中其他對象永遠處於等待,甚至整個內存回收系統崩潰。finallize()方法是對象逃脫死亡 的最後一次機會,在finalize方法中,如果對象把this(自己)指向了一個有效值(一個類變量或者對象的成員變量),則自我拯救成功,被移除“即將回收”的集合,如果沒有自救成功,則基本就被回收掉了。

package gc;
/**
 * 
 * @author hxl
 * 1.對象可以在被GC時自救(執行finalize方法)
 * 2.自救機會只有一次,因爲虛擬機只執行一次一個對象的finalize方法
 */
public class FinalizeEscapeGc{
	public static FinalizeEscapeGc SAVE_HOOK = null;
	
	public void isAlive(){
		System.out.println("我還活着");
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("finalize方法被執行");
		FinalizeEscapeGc.SAVE_HOOK = this;
	}
	
	public static void main(String[] args) throws InterruptedException {
		SAVE_HOOK = new FinalizeEscapeGc();
		
		//對象第一次成功自救
		SAVE_HOOK = null;
		System.gc();
		//因爲finalize()優先級很低,所以先暫停1秒等待他被執行
		Thread.sleep(1000);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		}else{
			System.out.println("是的,我已經確定死亡");
		}
		
		//對象第二次自救失敗
		SAVE_HOOK = null;
		System.gc();
		//因爲finalize()優先級很低,所以先暫停1秒等待他被執行
		Thread.sleep(1000);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		}else{
			System.out.println("是的,我已經確定死亡");
		}
	}
}

添:

我們通常會認爲方法區中的內存是沒有垃圾收集的,但是對於廢棄的常量和無用的類,也是可以(不是必須)被回收的。

判斷是否是無用的類有三個條件(滿足了,就判定爲無用的類):

A. 加載改類的類加載器ClassLoader已經被回收

B. 該類的所有實例(Java堆中)全部被回收

C. 該類所對應的Class對象(java.lang.Class)沒有在任何地方被引用(反射)

 

在下一篇文章中,我們將要學習的是垃圾收集算法,也就是 垃圾收集如何進行回收的。

 

 

 

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