天天看點

JVM進階特性與最佳實戰(四)————垃圾收集算法

推薦閱讀:

JVM進階特性與最佳實戰(一)————JAVA記憶體區域

JVM進階特性與最佳實戰(二)————對象的建立過程,記憶體布局,通路定位

JVM中字元串常量池的詳細剖析

JVM進階特性與最佳實戰(三)————如何判斷對象已死?

引言

在讨論垃圾回收算法之前,我必須得補充下昨天我們讨論的JVM進階特性與最佳實戰(三)————如何判斷對象已死?。我們先來看下就算在不可達的隊列中,對象如何活過來?請看代碼:

package JVM進階特性與實戰.對象死裡逃生;

/**
 * Author:haozhixin
 * Func:  對象在垃圾收集标記後的死裡逃生
 * Date:  20190817
 */
public class AliveFormGC {
	public static AliveFormGC SAVE_HOOK=null;

	public void isAlive(){
		System.out.println("oh  im alive now");
	}
	@Override
	protected void finalize() throws Throwable{
		super.finalize();
		System.out.println("finalize method executed!!");
		//重新賦引用
		AliveFormGC.SAVE_HOOK= this;
		System.out.println("1.SAVE_HOOK  value is :"+SAVE_HOOK);
	}

	public static void main(String [] args) throws Throwable{
		SAVE_HOOK = new AliveFormGC();

		SAVE_HOOK=null;
		//調用finalize方法。
		System.gc();
		//因為finalize優先級低,請下邊的方法稍微等一下。
		Thread.sleep(500);
		//第一次竟然活過來了?
		System.out.println("2.SAVE_HOOK  value is :"+SAVE_HOOK);
		if(SAVE_HOOK!=null){
			SAVE_HOOK.isAlive();
			System.out.println("3.SAVE_HOOK  value is :"+SAVE_HOOK);
		}else {
			System.out.println("oh  im dead now");
			System.out.println("4.SAVE_HOOK  value is :"+SAVE_HOOK);
		}


		SAVE_HOOK=null;
		System.gc();
		Thread.sleep(500);
		//第二次沒有活過來?
		//是因為任何一個對象的finalize方法都隻會被系統自動調用一次。面臨下次回收的時候,finalize方法不會再被執行
		System.out.println("5.SAVE_HOOK  value is :"+SAVE_HOOK);
		if(SAVE_HOOK!=null){
			SAVE_HOOK.isAlive();
			System.out.println("6.SAVE_HOOK  value is :"+SAVE_HOOK);
		}else {
			System.out.println("oh  im dead now");
			System.out.println("7.SAVE_HOOK  value is :"+SAVE_HOOK);
		}
	}
}

           

運作結果:

finalize method executed!!
1.SAVE_HOOK  value is :JVM進階特性與實戰.對象死裡逃生[email protected]
2.SAVE_HOOK  value is :JVM進階特性與實戰.對象死裡逃生[email protected]
oh  im alive now
3.SAVE_HOOK  value is :JVM進階特性與實戰.對象死裡逃生[email protected]
5.SAVE_HOOK  value is :null
oh  im dead now
7.SAVE_HOOK  value is :null
           

思路分析我寫到代碼備注裡了,大家仔細分析,下面我們言歸正傳,繼續來講垃圾回收算法。

标記-清除算法

  • 思路

    算法分為标記和清除兩部分,首先标記出所有需要回收的對象,在标記完成後統一回收所有被标記的對象,他的标記過程就是對象的标記判定,這一點如果還有不清楚的去看我的部落格補充下基礎知識。JVM進階特性與最佳實戰(三)————如何判斷對象已死?。

  • 特點

    (1)這個方法是最基本的算法,後續的收集算法都是基于這個标記清除算法演變的。

    (2)标記和清除的效率太低,影響回收速度。

    (3)标記清除後會産生大量的不連續的記憶體碎片。如果碎片過多,在進行下一次大對象的記憶體配置設定時,記憶體不夠會提前觸發另一次的垃圾回收動作。

複制算法

  • 思路

    他将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊,當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。

  • 特點:

    (1)每次都是對整個半區進行記憶體回收,記憶體配置設定時不用考慮記憶體碎片等複雜情況。

    (2)實作思路簡單,而且運作高效。

    (3)但是每次都把記憶體分為兩塊,可用的卻隻有一塊,代價有點大。

    (4)如果對象存活率較高的時候,要進行較多的複制,效率也會變的低。

  • 優化

    IBM公司的專門研究表明,新生代中的對象98%都是“朝生夕死”的,是以并不需要按照1:1的比例來劃分記憶體空間。而是将記憶體分為一塊較大的Eden空間和兩塊較小的Survivior空間,每次使用Eden和其中的一塊Survivor空間。當回收的時候,将Eden和Survivior的存活的對象一次性複制到另外一塊Survivior,然後清理之前的Eden和Survivor。

    這裡我提一下,當Survivor記憶體不夠了,會跟其他記憶體(老年代)進行記憶體的配置設定擔保,至于怎麼實作的這裡不多提了。

标記-整理算法

  • 思路

    标記的過程與标記-清除算法一緻,但是後續步驟增加了使對象向一端移動,直接清理另外一端的記憶體。

  • 特點

    主要針對老年代

分代收集算法

  • 思路

    根據對象的存活周期将記憶體分為幾塊。一般是把JAVA堆分為新生代和老年代。根據各個年代的特點選擇合适的算法,比如,在新生代中,每次垃圾回收都發現有大批的對象死去,隻有少量存活,那就選用複制算法。而老年代因為對象存活率高,沒有額外空間進行配置設定擔保,是以要使用标記-清除, 标記-整理的算法。

總結

大家主要想明白理論就可以了,一般的JAVA面試中也不會對虛拟機進行代碼級深入的提問。下一節我們講解下目前主流的垃圾收集器。

作者:select you from me

來源:CSDN

轉載請聯系作者獲得授權并注明出處。

繼續閱讀