推薦閱讀:
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
轉載請聯系作者獲得授權并注明出處。