主頁 > 軟體設計 > 深入理解JVM & G1 GC:深度決議七種垃圾收集演算法!它們是如何實作的?原理是什么?

深入理解JVM & G1 GC:深度決議七種垃圾收集演算法!它們是如何實作的?原理是什么?

2021-01-28 11:21:14 軟體設計

目錄

  • 一、參考計數法
  • 二、根搜索演算法
  • 三、標記·清除演算法( Mark-Sweep )
  • 四、復制演算法 (Copying)
  • 五、標記-壓縮演算法 (Mark-Compact)
  • 六、增量演算法 (Incremental Collecting )
  • 七、分代收集演算法 (Generational Collecting)

目前有兩種比較常見的垃圾標記演算法,分別是 參考計數演算法根搜索演算法 ,參考計數器在微軟的 COM 組件技術 中、 Adodb 的 ActionScript3 中都有使用,

一、參考計數法

參考計數法(Refernce Counting 在 GC 執行垃圾回收之前,首先需要區分出記憶體中哪些是存活物件,哪些是已經死亡的物件,只有被標記為己經死亡的物件, 才會在執行垃圾回收時,釋放掉其所占用的記憶體空間,因此這個程序我們可以稱為 垃圾標記階段

參考計數器的實作很簡單,對于一個物件A,只要有任何一個物件參考了A,則A的參考計數器就加1,當參考失效時,參考計數器就減1,只要物件A的參考計數器的值為0,則物件A就不可能再被使用,也就是說,參考計數器的實作只需要為每個物件配置一個整形的計數器即可,參考計數器演算法的一大優勢就是不用等待記憶體不夠用的時候,才進行垃圾的回收,完全可以在賦值操作的同時檢查計數器是否為0,如果是的話就可以立即回收,

但是參考計數器有一個嚴重的問題,即無法處理回圈參考的情況,一個簡單的回圈參考問題的描述如下 有物件A和物件B,物件A中含有物件B的參考,物件B中含有物件A的參考,此時,物件A和物件B的參考計數器都不為0,但是在系統中卻不存在任何第3個物件參考了 ,也就是說,A和B是應該被回收的垃圾物件,但由于垃圾物件間相互參考,從而使垃圾回收器無法識別,引起 記憶體泄漏

如下圖所示,構造了一個串列,將最后一個元素的 next 屬性指向第一個元素,即參考第一個元素,從而構成回圈參考,這個時候如果將串列的頭 head 賦值為 null,此時串列的各個元素的計數器都不為0,同時也失去了對串列的參考控制,從而導致串列元素不能被回收,

參考計數器擁有一些特性,首先它需要單獨的欄位存盤計數器,這樣的做法增加了存盤空間的開銷,其次,每次賦值都需要更新計數器,這增加了時間開銷,再者,垃圾物件便于辨識,只要計數器為0,就可作為垃圾回收,接下來它能方便及時地回收垃圾,沒有延遲性,最后不能解決回圈參考的問題,正是由于最后一條致命缺陷,導致在 Java 的垃圾回收器中沒有使用這類演算法,


參考資料:《深入理解JVM & G1 GC》
本文文字內容偏多,為避免閱讀疲勞,請添加助理VX:C18173184271,備注一下CSDN+作業年限!免費獲取
以便能夠充分理解學習!

二、根搜索演算法

Hotspot 和大部分 JVM 都是使用 根搜索演算法 作為垃圾標記的演算法實作,前面介紹過的參考計數演算法盡管實作簡單,執行效率也不錯,但是該演算法本身卻存在一個較大的弊端,甚至會影響到垃圾標記的準確性,由于參考計數演算法會為程式中的每一個物件都創建一個私有的參考計數器,當目標物件被其他存活物件參考時,參考計數器中的值則會加1,不再參考時便會減 1,當參考計數器中的值為0的時候,就意味著該物件己經不再被任何存活物件參考,可以被標記為垃圾物件,采用這種方式看起來似乎沒有任何問題,但是如果一些明顯已經死亡了的物件盡管沒有被任何的存活物件參考,但是它們彼此之間卻存在相互參考時,參考計數器中的值則永遠不會為0,這樣便會導致 GC 在執行記憶體回收時永遠無法釋放掉這種無用物件所占用的記憶體空間,極有可能引發記憶體泄漏,

相對于參考計數演算法而言,根搜索演算法不僅同樣具備 實作簡單執行高效 等特點,更重要的是該演算法可以有效地解決在參考計數演算法中一些己經死亡的物件因相互參考而導致的無法正確被標記的問題,防止記憶體泄漏的發生,簡單來說,根搜索演算法是以根物件集合為起始點按照從上至下的方式搜索被根物件集合所連接的目標物件是否可達(使用根搜索演算法后,記憶體中的存活物件都會被根物件集合直接或間接連接著),如果目標物件不可達,就意味著該物件己經死亡,便可以在 instanceOopDesc Mark World 中將其標記為垃圾物件,在根搜索演算法中,只有能夠被根物件集合直接或者間接連接的物件才是存活物件,在 Hotspot 中,根物件集合中包含了5個元素, Java 堆疊內的物件參考、本地方法堆疊內的物件參考、運行時常量池中的物件參考、方法區中類靜態屬性的物件參考以及與 個類對應的唯一資料型別的 Class 物件,

注解①Hotspot 代碼中用 instanceOopDesc 來表示 Java 物件,而該類繼承 oopDesc ,所以 Hotspot 中的 Java 物件 也自然擁有 oopDesc 所宣告的頭部,

注意,在根搜索演算法中不可達的物件,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段,要真正宣告一個物件死亡,至少要經歷兩次標記程序,如果物件在進行根搜索后發現沒有與 GC Roots 相連接的參考鏈,那它將會被第一次標記井且進行一次篩選,篩選的條件是此物件是否有必要執行 finalize() 方法,當物件沒有覆寫 finalize() 方法,或者 finalize() 方法 已經被虛擬機呼叫過,虛擬機將這兩種情況都視為 “ 沒有必要執行 ” ,如果這個物件被判定為有必要執行 finalize() 方法,那么這個物件將會被放置在一個名為 F-Queue 的佇列之中,并在稍后由一條由虛擬機自動建立的、低優先級的 Finalizer 執行緒 去執行,這里所謂的“執行”是指虛擬機會觸發這個方法,但并不承諾會等待它運行結束,這樣做的原因是,如果 個物件在 finalize()方法 中執行緩慢,或者發生了死回圈(更極端的情況),很可能會導致 F-Queue 佇列 中的其他物件永久處于等待狀態,甚至導致整個記憶體回收系統崩潰, finalize() 方法 是物件逃脫死亡命運的最后一次機會,稍后 GC 將對 F- Queue 中的物件進行第二次小規模的標記,如果物件要在 finalize () 中成功拯救自己一一只要重新與參考鏈上的任何一個物件建立關聯即可,譬如把自己(this 關鍵字)賦值給某個類變數或物件的成員變數,那在第二次標記時它將被移除出“即將回收”的集合,如果物件這時候還沒有逃脫,那它就真的離死不遠了,從下面的代碼中可以看到一個物件的 finalize ()被執行,但是它仍然可以存活,

逃脫回收實驗

public class FinalizeEscapeGC { 
	public static FinalizeEscapeGC SAVE_HOOK = null; 
	public void isAlive() { 
		System.out.println("yes, i am still alive");
	}
	
	@Override 
	protected void finalize() throws Throwable { 
		super.finalize() ; 
		System.out.println("finalize mehtod executed !"); 
		FinalizeEscapeGC.SAVE_HOOK = this; 
	}
	
	public static void main(String[] args) throws Throwable { 
		SAVE_HOOK = new FinalizeEscapeGC (); 
		//物件第 次成功拯救自己
		SAVE_HOOK = null ; 
		System.gc(); 
		//因為 Finalizer 方法優先級很低,暫停 秒,以等待它
		Thread.sleep(5OO); 
		if (SAVE_HOOK != null) { 
			SAVE_HOOK.isAlive() ; 
		} else {
			System.out.println("no , i am dead"); 
		}
		//下面這段代碼與上面的完全相同 ,但是這次自救卻失敗了
		SAVE_HOOK = null ; 
		System.gc(); 
		//因為 Finalizer 方法優先級很低,暫停 0.5 秒,以等待它
		Thread .sleep(500) ; 
		if (SAVE_HOOK ! = null) { 
			SAVE_HOOK.isAlive(); 
		} else { 
			System.out.println("no, i am dead");
		}
	}
}

輸出如下面代碼所示:逃脫回收實驗運行輸出

finalize mehtod executed! 
yes, i am still alive 
no , i am dead

從上面代碼的運行結果可以看到, SAVE_HOOK 物件finalize() 方法 確實被 GC 收集器 觸發過,并且在被收集前成功逃脫了,另外一個值得注意的地方就是,代碼中有兩段完全樣的代碼片段,執行結果卻是一次逃脫成功,一次失敗,這是因為任何一個物件的 finalize() 方法 都只會被系統自動呼叫一次,如果物件面臨下一次回收,它的 finalize() 方法 不會被再次執行,因此第2段代碼的自救行動失敗了,

三、標記·清除演算法( Mark-Sweep )

當成功區分出記憶體中存活物件和死亡物件后, GC 接下來的任務就是執行垃圾回收,釋放掉無用物件所占用的記憶體空間,以便有足夠的可用記憶體空間為新物件分配記憶體,目前在 JVM中比較常見的三種垃圾收集演算法是 標記-清除演算法( Mark-Sweep )復制演算法( Copying )標記-壓縮演算法( Mark-Compact ),在介紹三種演算法之前,我們先來通過下圖看看它們之間的區別,

標記-清除演算法( Mark-Sweep ) 是一種非常基礎和常見的垃圾收集演算法,該演算法被 J.McCarthy 等人在 1960 年提出并成功地發明井應用于 Lisp 語言,以餐巾紙作為示例, 午餐程序中,餐廳的所有人都根據自己的需要取用餐巾紙, 當垃圾收集機器人想收集廢舊餐巾紙的時候,它讓所有用餐的人先停下來,然后,依次詢問餐廳里的每一個人:“你正在用餐巾紙嗎?你用的是哪一張餐巾紙?”機器人根據每個人的回答將人們正在使用的餐巾紙畫上記號,詢問程序結束后,機器人在餐廳里尋找所有散落在餐桌上且沒有記號的餐巾紙(這些顯然都是用過的廢舊餐巾紙),把它們統統扔到垃圾箱里,

回到演算法本身,演算法涉及幾個概念,先來了解 mutatorcollector ,這兩個名詞經常在垃圾收集演算法中出現, collector 指的就是 垃圾收集器 ,而 mutator 是指 除了垃圾收集器之外的部分 ,比如說我們的應用程式本身, mutator 的職責一般是 NEW (分配記憶體)、READ (從記憶體中讀取內容)WRITE (將內容寫入記憶體),而 collector 就是回收不再使用的記憶體來供 mutator 進行 NEW 操作的使用, mutator 根物件 一般指的是分配在堆記憶體之外,可以直接被 mutator 直接訪問到的物件,一般是指靜態/全域變數以及 ThreadLocal 變數,

注解②: Java 中,存盤在 Java .lang.ThreadLocal 中的變數和分配在枝上的變數方法內部的臨時變數等都屬于此類,

標記-清除演算法 將垃圾回收分為兩個階段,標記階段清除階段 ,在標記階段, collectormutator 根物件 開始進行遍歷,對從 mutator 根物件 可以訪問到的物件都打上一個標識,一般是在物件的 header 中,將其記錄為可達物件,而在清除階段, collector 對堆記憶體( heap memory ) 從頭到尾進行線性的遍歷,如果發現某個物件沒有標記為可達物件,通過讀取物件的 header 資訊,則將其回收,一種可行的實作是,在標記階段首先通過根節點,標記所有從根節點開始的可達物件,因此,未被標記的物件就是未被參考的垃圾物件,然后,在清除階段,清除所有未被標記的物件,

前面說過,標記-清除演算法 的執行程序分為 “標記”“清除” 兩大階段,這種分步執行的思路奠定了現代垃圾收集演算法的思想基礎,與參考計數演算法不同的是,標記-清除演算法不需要運行環境監測每一次記憶體分配和指標操作,而只要在“標記”階段中跟蹤每一個指標變數的指向,用類似思路實作的垃圾收集器也常被后人統稱為 跟蹤收集器Tracing Collector ),

標記-清除演算法 最大的問題是存在大量的空間碎片,因為回收后的空間是不連續的,在物件的堆空間分配程序中,尤其是大物件的記憶體分配,不連續的記憶體空間的作業效率要低于連續的空間,

相對于另外兩種記憶體回收演算法而 標記-清除演算法(Mark-Sweep) 不僅 執行效率低下,更重要的是,由于被執行記憶體回收的無用物件所占用的記憶體空間有可能是一些不連續的記憶體塊,不可避免地會產生一些記憶體碎片,從而導致后續沒有足夠的可用記憶體空間分配給較大的物件,

四、復制演算法 (Copying)

為了解決 標記-清除演算法 在垃圾收集效率方面的缺陷,M.L.Minsky 于1963 年發表了著名的論文, “一種使用雙存盤區的 Lisp 語言垃圾收集器 (A LISP Garbage Collector Algorithm Using Serial Secondary Storage )” ,M.L.Minsky 在該論文中描述的演算法被人們稱為 復制演算法,它也被 M.L.Minsky 本人成功地引入到了 Lisp 個實作版本中,

標記-清除演算法 后來被引入 JVM 中,提升了 GC 在垃圾標記和記憶體釋放這兩個階段的執行效率,還是采用之前的餐廳示例,餐廳被垃圾收集機器人分成南區和北區兩個大小完全相同的部分,午餐時,所有人都先在南區用餐(因為空間有限,用餐人數自然也將減少一半) ,用餐時可以隨意使用餐巾紙,當垃圾收集機器人認為有必要回收廢舊餐巾紙時,它會要求所有用餐者以最快的速度從南區轉移到北區,同時隨身攜帶自己正在使用的餐巾紙,等所有人都轉移到北區之后,垃圾收集機器人只要簡單地把南區中所有散落的餐巾紙扔進垃圾箱就算完成任務了,下一次垃圾收集的作業程序也大致類似,唯一的不同只是人們的轉移方向變成了從北區到南區,如此回圈往復,每次垃圾收集都只需簡單地轉移(也就是復制)一次,垃圾收集速度無與倫比一一當然,對于用餐者往返奔波于南北兩區之間的辛勞 ,垃圾收集機器人是絕不會流露出絲毫憐憫的,

回到演算法本身,復制演算法首先將活著的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時將正在使用的記憶體中的存活物件復制到未被使用的記憶體塊中,之后清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,最后完成垃圾回收,

如果系統中的垃圾物件很多,復制演算法需要復制的存活物件數量并不會太大,因此在真正需要垃圾回收的時刻,復制演算法的效率是很高的,又由于物件在垃圾回收程序中統一被復制到新的記憶體空間中,因此,可確保回收后的記憶體空間是沒有碎片的,該演算法的缺點是將系統記憶體折半

Java 年輕代串行垃圾回收器中使用了復制演算法的思想,年輕代分為 Eden 空間From 空間To 空間 3個部分,其中 From 空間To 空間 可以視為用于復制的兩塊大小相同、地位相等,且可進行角色互換的空間塊, From To 空間 也稱為 Survivor 空間,即 幸存者空間,用于存放未被回收的物件

在垃圾回收時, Eden 空間 中的存活物件會被復制到未使用的 Survivor 空間 中(假設是 To),正在使用的 Survivor 空間(假設是 From )中的年輕物件也會被復制到 To 空間中(大物件,或者老年物件會直接進入老年代,如果 To 空間己滿,則物件也會直接進入老年代),此時, Eden 空間From 空間 中的剩余物件就是垃圾物件,可以直接清空, To 空間則存放此次回收后的存活物件,這種改進的復制演算法既保證了空間的連續性,又避免了大量的記憶體空間浪費,

基于分代的概念, Java 堆區如果還要更進一步細分的話,還可以劃分為年輕代( YoungGen)老年代( OldGen ),其中年輕代又可以被劃分為 Eden 空間From Survivor 空間To Survivor 空間,在 HotSpot 中, Eden 空間 和另外兩個 Survivor 空間 默認所占的比例是8:1, 當然開發員可以通過選項“-XX SurvivorRatio ”調整這個空間比例 ,當執行一次 MinorGC (年輕代的垃圾回收)時, Eden 空間中的存活物件會被復制到 To 空間內,井且之前已經經歷過一次 MinorGC 并在 From 空間中存活下來的物件如果還年輕的話同樣也會被復制到 To 空間內,需要注意的是,在滿足兩種特殊情況下, Eden From 空間 中的存活物件將不會被復制到 To 空間內,首先是如果存活物件的分代年齡超過選項“ XX: MaxTenuringThreshold ”所指定的閥值時,將會直接晉升到老年代中,其次當 To 空間的容量達到閥值時,存活物件同樣也是直接晉升到老年代中當所有的存活物件都被復制到 To 空間或者晉升到老年代后,剩下的均為垃圾物件,這就意味GC 可以對這些已經死亡了的物件執行一次 MinorGC ,釋放掉其所占用的記憶體空間,

當執行完 Minor GC 之后, Eden 空間From 空間 將會被清空,而存活下來的物件則會被全部存盤在 To 空間內 ,接下來 From 空間和 To 空間將會互換位置,其實復制演算法無非就是使 To Survivor 空間作為一個臨時的空間交換角色,務必需要保證兩塊空間中一塊必須是空的,這就是復制演算法,盡管復制演算法能夠高效執行 MinorGC ,但是它卻并不適用于老年代中的記憶體回收,因為老年代中物件的生命周期都比較長,甚至在某些極端的情況下還能夠與 JVM 的生命周期保持一致,所以如果老年代也采用復制演算法執行記憶體回收,不僅需要額外的時間和空間,而且還會導致較多的復制操作影響到 GC 的執行效率,

總的來說,由于 JVM 中的絕大多數物件都是瞬時狀態的,生命周期非常短暫,所以復制演算法被廣泛應用于年輕代中,磁區、復制的思路不僅大幅提高了垃圾收集的效率,而且也將原本繁紛復雜的記憶體分配演算法變得前所未有的簡明和扼要(既然每次記憶體回收都是對整個半區的回收,記憶體分配時也就不用考慮記憶體碎片等復雜情況,只要移動堆頂指標,按順序分配記憶體就可以了),這簡直是個奇跡!不過,任何奇跡的出現都有一定的代價,在垃圾收集技術中,復制演算法提高效率的代價是人為地將可用記憶體縮小了一半,

五、標記-壓縮演算法 (Mark-Compact)

標記-清除演算法 的確可以應用在老年代中,但是該演算法不僅執行效率低下,而且在執行完記憶體回收后還會產生記憶體碎片,所以 JVM 的設計者在此基礎之上進行了改進,標記-壓縮演算法 由此誕生,標記-壓縮演算法標記-清除演算法復制演算法 的有機結合,把標記-清除演算法在記憶體占用上的優點和復制演算法在執行效率上的特長綜合起來,這是所有人都希望看到的結果,不過,兩種垃圾收集演算法的整合并不像 1加1 等于2 那樣簡單,必須引入一些全新的思路,

1970 年前后, G. L. Steele、C. J. Chene 和 D.S. Wise 等研究者陸續找到了正確的方向,標記-壓縮演算法 的輪廓也逐漸清晰了起來,還是采用之前的餐廳示例,在我們熟悉的餐廳里,這一次,垃圾收集機器人不再把餐廳分成兩個南北區域了,需要執行垃圾收集任務時,機器人先執行 標記-清除演算法 的第一個步驟,為所有使用中的餐巾紙畫好標記,然后,機器人命令所有就餐者帶上有標記的餐巾紙向餐廳的南面集中,同時把沒有標記的廢舊餐巾紙扔向餐廳北面,這樣來,機器人只需要站在餐廳北面,懷抱垃圾箱,迎接撲面而來的廢舊餐巾紙就行了,

回到演算法本身, 成功標記出記憶體中的垃圾物件后,標記-壓縮演算法 會將所有的存活物件都移動到一個規整且連續的記憶體空間中,然后執行 Full GC (老年代的垃圾回收,或者被稱為 or GC ) 回收無用物件所占用的記憶體空間,當成功執行壓縮之后,己用和未用的記憶體都各自一邊,彼此之間維系著一個記錄下一次分配起始點的標記指標,當為新物件分配記憶體時,則可以使用 指標碰撞( Bump the Pointer 技術修改指標的偏移量將新物件分配在第一個空閑記憶體位置上,為新物件分配記憶體帶來便捷,

HotSpot 中,基于分代的概念, GC 所使用的記憶體回收演算法必須結合年輕代和老年代各自的特點,簡單來說,就是針對不同的代空間,從而結合使用不同的垃圾收集演算法,為年輕代選擇的垃圾收集演算法通常是以速度優先,因為年輕代中所存盤的瞬時物件生命周期非常短暫,可以有針對性地使用 復制演算法,因此執行 Minor GC 時, 一定要保持高效和快速,而年輕代中的生存空間通常都比較小,所以回收年輕代時一定會非常頻繁,但老年代通常使用更節省記憶體的回收演算法,因為老年代中所存盤的物件生命周期都非常長,并且老年代占據了大部分的堆空間,所以老年代的 Full GC 并不會跟年輕代的 Minor GC 一樣頻繁,不過一旦程式中發生一次 Full GC ,將會耗費更長的時間來完成,那么在老年代中使用標記-清除演算法或者標記-壓縮演算法執行垃圾回收將會是不錯的選擇,

復制演算法 的高效性是建立在存活物件少、垃圾物件多的前提下的,這種情況在年輕代經常發生,但是在老年代更常見的情況是大部分物件都是存活物件,如果依然使用復制演算法,由于存活的物件較多,復制的成本也將很高,標記-壓縮演算法 是一種老年代的回收演算法,它在 標記-清除演算法 的基礎上做了一些優化,也首先需要從根節點開始對所有可達物件做一次標記,但之后,它并不是簡單地清理未標記的物件,而是將所有的存活物件壓縮到記憶體的 端,之后,清理邊界外所有的空間,這種方法既避免了碎片的產生,又不需要兩塊相同的記憶體空間,因此,其性價比比較高,

標記-壓縮演算法 的總體執行效率高于 標記-清除演算法,又不像 復制演算法 那樣需要犧牲一半的存盤空間,這顯然是一種非常理想的結果,在許多現代的垃圾收集器中,人們都使用了 標記-壓縮演算法 或其改進版本,

六、增量演算法 (Incremental Collecting )

在垃圾回收程序中,應用軟體將處于一種 Stop the World 的狀態,在 Stop the World 狀態下,應用程式所有的執行緒都會掛起,暫停一切正常的作業,等待垃圾回收的完成,如果垃圾回收時間過長,應用程式會被掛起很久,將嚴重影響用戶體驗或者系統的穩定性,為了解決這個問題,即對實時垃圾收集演算法的研究直接導致了增量收集演算法的誕生,

最初,為了進行實時垃圾收集,可以設計一個多行程的運行環境,比如用一個行程執行垃
圾收集作業,另一個行程執行程式代碼,這樣一來,垃圾收集作業看上去就仿佛是在后臺悄悄完成的,不會打斷程式代碼的運行,在收集餐巾紙的例子中,這一思路可以被理解為垃圾收集機器人在人們用餐的同時尋找廢棄的餐巾紙并將它們扔到垃圾箱里,這個看似簡單的思路會在設計和實作時碰上行程間沖突的難題,比如說,如果垃圾收集行程包括標記和清除兩個作業階段,那么,垃圾收集器在第一階段中辛辛苦苦標記出的結果很可能被另一個行程中的記憶體操作代碼修改得面目全非,以至于第二階段的作業沒有辦法開展,

M. L. Minsky 和 D. E. Knuth 對實時垃圾收集程序中的技術難點進行了早期的研究, G. L. Steele 1975 年發表了題為 “多行程整理的垃圾收集 Multiproc sing Compactifying Garbage Collection ” 的論文,描述了一種被后人稱為 “Minsky-Knuth-Steele 演算法” 的實時垃圾收集演算法, E.W.Dijkstra、L.Lamport、R.R.Fenichel 和 J.C.Yochelson 等人也相繼在此領域做出了各自的貢獻, 1978 年, H.G .Baker 發表了 “串行計算機上的實時表處理技術 (List Processing in Real Time on a Serial Computer)” 一文,系統闡述了多行程環境下用于垃圾收集的 增量收集演算法

增量演算法 的基本思想是,如果一次性將所有的垃圾進行處理,需要造成系統長時間的停頓,那么就可以讓 垃圾收集執行緒應用程式執行緒 交替執行,每次,垃圾收集執行緒只收集一小片區域的記憶體空間,接著切換到應用程式執行緒,依次反復,直到垃圾收集完成,使用這種方式,由于在垃圾回收程序中,間斷性地還執行了應用程式代碼,所以能減少系統的停頓時間,但是,因為執行緒切換和背景關系轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降,

總的來說,增量收集演算法的基礎仍是傳統的標記 清除和復制演算法,增量收集演算法通過對進
程間沖突的妥善處理,允許垃圾收集行程以分階段的方式完成標記、清理或復制作業,

七、分代收集演算法 (Generational Collecting)

1980 年前后,善于在研究中使用統計分析知識的技術人員發現,大多數記憶體塊的生存周期都比較短,垃圾收集器應當把更多的精力放在檢查和清理新分配的記憶體塊上,還是餐巾紙的例子,如果垃圾收集機器人足夠聰明,事先摸清了餐廳里每個人在用餐時使用餐巾紙的習慣一一比如有些人喜歡在用餐前后各用掉一張餐巾紙,有的人喜歡自始至終攥著一張餐巾紙不放,有的人則每打一個噴嗖就去用一張餐巾紙一一機器人就可以制定出更完善的餐巾紙回收計劃,并總是在人們剛扔掉餐巾紙沒多久就把垃圾撿走,這種基于統計學原理的做法當然可以讓餐廳的整潔度成倍提高,D. E. Knuth、T. Knight、G. Sussman 和 R. Stallman 等人對記憶體垃圾的分類處理做了最早的研究, 1983 年, H. Lieberman 和 C.Hewitt 發表了題為 “基于物件壽命的一種實時垃圾收集器( Real-Time Garbage Collector Based on the Lifetimes of ects ” 的論文,這篇著名的論文標志著分代收集演算法的正式誕生,此后,在 H. G. Baker、R. L. Hudson、 J.E. B. Moss 等人的共同努力下,分代收集演算法逐漸成為了垃圾收集領域里的主流技術,

根據垃圾回收物件的特性,使用合適的演算法回收,分代就是基于這種思想,它將記憶體區間根據物件的特點分成幾塊,根據每塊記憶體區間的特點,使用不同的回收演算法以提高垃圾回收的效率,

Hotspot 虛擬機 為例,它將所有的新建物件都放入稱為年輕代的記憶體區域,年輕代的特點是物件會很快回收,因此,在年輕代就選擇效率較高的復制演算法,當一個物件經過幾次回收后依然存活,物件就會被放入稱為老年代的記憶體空間,在老年代中,幾乎所有的物件都是經過幾次垃圾回收后依然得以幸存的,因此,可以認為這些物件在一段時期內,甚至在應用程式的整個生命周期中,將是常駐記憶體的,如果依然使用復制演算法回收老年代,將需要復制大量物件,再加上老年代的回收性價比也要低于年輕代,因此這種做法也是不可取的,根據分代的思想,可以對老年代的回收使用與年輕代不同的 標記-壓縮演算法,以提高垃圾回收效率,

總的來說,分代收集演算法是基于對物件生命周期分析后得出的垃圾回收演算法,它把物件分為年輕代、老年代、持久代,對不同生命周期的物件使用不同的演算法(上述方式中的一個)進行回收, JVM 垃圾回收器(從 J2SE1.2 開始)都是使用此演算法的,


如果你需要這份完整版的《深入理解JVM & G1 GC》,只需你多多支持我這篇文章,

多多支持,即可免費獲取資料——三連之后(承諾:100%免費)

快速入手通道:添加助理VX:C18173184271,備注一下CSDN+作業年限! 免費獲取!誠意滿滿!!!
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/253518.html

標籤:其他

上一篇:常見的幾種排序

下一篇:Filter-Policy

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more