java基础巩固-宇宙第一AiYWM:为了维持生计,JVM_Part4~(4种垃圾收集算法(标清、标整、复制、分代)、判断是否是垃圾(引用计数、根可达算法))、四种引用类型、整起
詳細的圖又需要的,評論區留言,后面發給你
JVM事件大概回顧一下,主要就辦了兩件事唄:(當然細枝末節需要大家自己扣扣)
- 小胡和敏小言去民政&局領證(類加載器和雙親委派機制,玩轉~類加載器和雙親委派機制)
- 一個打手和一堆打手地盤的故事(java內存區域或者運行時數據區域的故事,玩轉~java內存區域或者運行時數據區域)
然后呢,最后剩下了幾個主要點:
- 打手們(線程們)私有的地盤他們自己可以打掃好,不用專人來幫忙
- 打手們共享的區域中有倆地需要專人來幫們善后,打掃。
- 打手們互相打斗完的舞臺(堆,人稱堆子哥),上面有很多臟牙套、xue漬、汗水…,需要專人來幫們善后,打掃。
- 民政&局處理完給類似小胡和敏小言類似的辦結婚證事件之后,會在方法區(人稱區子哥)有廢棄的紙張、圖片等材料廢材,所以也需要專人來幫們善后,打掃。
咱們上面所謂的來打掃的專人叫做GC垃圾回收器,是一個保潔集團。人家的公司標語倒也貼切“我們的服務宗旨是,就是讓堆子哥和區子哥,永遠~ 靚~白 ~白,無(垃)圾可施(工)”
好啦,開始施工唄,遠方傳來保潔施工隊的陣陣施工聲~
一個打手坐在拳臺角上邊擦汗邊斜眼問道,你們這可別把我有用的東西給我錯當垃圾給清了,到底有沒有譜呀。(打手心里念叨,要不是我要陪我媳婦去疊烤肉,我早都自己來了)。
保潔隊長說,大鍋呀,nen(你)瞧瞧nen(你)說的這是啥話,我們是專業的哦。接著翹起指頭說到,我們有一套專門的標準,里面有好幾個方法來判斷出您們客戶哪些是垃圾(可回收)哪些不是垃圾(不能回收),有時候也說對象存活著就不是垃圾,沒存活的對象就是垃圾。
打手:nen倒是說呀。
保潔隊長:額(我)們有好多方法呢。bai急呀,先給你說 堆子哥 要得不呀?
打手:要得要得。
- 引用計數法:(給每一個對象設置一個引用計數器,當有一個地方引用該對象的時候,引用計數器就+1,引用失效時,引用計數器就-1;當引用計數器為0的時候【任何時候計數器為 0 的對象就是不可能再被使用的】,就說明這個對象沒有被引用,也就是垃圾對象,等待回收)
- nen知道堆中的每個對象吧,額(我)們會 給每個對象的引用貼一張小紙條,就像這樣,呸,再用口水貼上去,然后我們有專門的儀器來記錄每個對象貼的紙條數。
- 優缺點:
- 優點:判定的效率也非常;這種算法是比較直接的找到垃圾,然后去回收,也被稱為"直接垃圾收集"。
- 缺點:如圖中所示,無法解決循環引用的問題,當A引用B,B也引用A的時候,此時AB對象的引用都不為0,此時也就無法垃圾回收(a 對象引用了 b 對象,b 對象也引用了 a 對象,a、b 對象卻沒有再被其他對象所引用了,其實正常來說這兩個對象已經是垃圾了,因為沒有其他對象在使用了,但是計數器內的數值卻不是 0,所以引用計數算法就無法回收它們),所以 一般主流虛擬機都不采用這個方法;
- 打手心里默念到,哇唔,口水…真…
- nen知道堆中的每個對象吧,額(我)們會 給每個對象的引用貼一張小紙條,就像這樣,呸,再用口水貼上去,然后我們有專門的儀器來記錄每個對象貼的紙條數。
- 可達性分析算法:(從一個被稱為GC Roots的對象為起始點向下搜索(搜索所走過的路徑稱為引用鏈(Reference Chain)),如果一個對象到GC Roots沒有任何引用鏈相連接時或者說GC Roots到這個對象不可達時,說明此對象不可用)。請注意,可達~可到達 ~,我們公司有個很長的桿子,我們會找個起點,然后站在起點(起點就是一個被稱為GC Roots的對象)上開戳,可達的(能戳到的)就是活著的(活著的就不是垃圾嘞,是不能回收的)
- 根可達算法也是JVM 默認使用的尋找垃圾算法,它的原理就是定義了一系列的根,我們把它稱為 “GC Roots” ,從 “GC Roots” 開始往下進行搜索,走過的路徑我們把它稱為 “引用鏈” ,當一個對象到 "GC Roots"之間沒有任何引用鏈相連時,那么這個對象就可以被當做垃圾回收了。
- 根可達算法就可以避免計數器算法不好解決的循環引用問題,當多個對象彼此之前有引用關系,但是沒有與"GC Roots"相連時,那么就會被當做垃圾所回收。
- 在java中可以作為GC Roots的對象有以下幾種:在java中有固定的GC Roots 對象和不固定的臨時GC Roots對象
- 固定的GC Roots:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調用的方法堆棧中使用到的參數、局部變量、臨時變量等
- 方法區中類靜態屬性引用的變量
- 在方法區中類靜態屬性引用的對象,譬如 Java 類的引用靜態變量
- 方法區常量池中常量引用的對象,譬如字符串常量池中的引用
- 本地方法棧JNI(譬如 Native 方法)引用的對象
- Java 虛擬機內部的引用,如基本數據類型對應的 Class 對象,一些常駐的異常對象(空指針異常、OOM等),還有類加載器。
- 所有被 Synchronized 同步鎖持有的對象
- 反應 Java 虛擬機內部情況的 JMXBean、JVMTI 中注冊的回調本地代碼緩存等。
- 臨時GC Roots:
- 為什么會有臨時的 GC Roots ?:目前的垃圾回收大部分都是分代收集和局部回收,如果只針對某一部分區域進行局部回收,那么就必須要考慮的當前區域的對象有可能正被其他區域的對象所引用,這時候就要將這部分關聯的對象也添加到 GC Roots 中去來確保根可達算法的準確性。這種算法是利用了逆向思維,找到使用的對象,剩下的就是垃圾,也被稱為"間接垃圾收集"。
- 固定的GC Roots:
- 垃圾回收器是怎樣尋找 GC Roots 的:根可達算法是通過 GC Roots 來找到存活的對象的,也定義了 GC Roots,那么垃圾回收器是怎樣尋找GC Roots 的呢?
- 首先,為了保證結果的準確性,GC Roots枚舉時是要在STW的情況下進行的,但是由于 JAVA 應用越來越大,所以也不能逐個檢查每個對象是否為 GC Root,那將消耗大量的時間。一個很自然的想法是,能不能用空間換時間,在某個時候把棧上代表引用的位置全部記錄下來,這樣到真正 GC 的時候就可以直接讀取,而不用再一點一點的掃描了。事實上,大部分主流的虛擬機也正是這么做的,比如 HotSpot ,它使用一種叫做 OopMap 的數據結構來記錄這類信息。
- OopMap 是做什么的?有什么好處?
- 我們知道,一個線程意味著一個棧,一個棧由多個棧幀組成,一個棧幀對應著一個方法,一個方法里面可能有多個安全點。gc 發生時,程序首先運行到最近的一個安全點停下來,然后更新自己的 OopMap ,記下棧上哪些位置代表著引用。枚舉根節點時,遞歸遍歷每個棧幀的 OopMap ,通過棧中記錄的被引用對象的內存地址,即可找到這些對象( GC Roots )。使用 OopMap 可以避免全棧掃描,加快枚舉根節點的速度。但這并不是它的全部用意。
- 安全點:從線程角度看,安全點可以理解成是在代碼執行過程中的一些特殊位置,當線程執行到這些位置的時候,說明虛擬機當前的狀態是安全的。比如:方法調用、循環跳轉、異常跳轉等這些地方才會產生安全點。如果有需要,可以在這個位置暫停,比如發生GC時,需要暫停所有活動線程,但是線程在這個時刻,還沒有執行到一個安全點,所以該線程應該繼續執行,到達下一個安全點的時候暫停,等待 GC 結束。
- 有兩種方式可以讓線程在垃圾回收的時候都跑到最近的安全點呢?
- 搶先式中斷:就是在stw的時候,先讓所有線程完全中斷,如果中斷的地方不在安全點上,然后再激活,直到運行到安全點的位置再中斷。
- 主動式中斷:在安全點的位置打一個標志位,每個線程執行都去輪詢這個標志位,如果為真,就在最近的安全點掛起。每個線程執行都去輪詢時,有沒有考慮如果有些線程處于sleep狀態怎么辦呢?為了解決有些線程處于sleep狀態這種問題,又引入了安全區域的概念(安全區域是指在一段代碼片中,引用關系不會發生改變,實際上就是一個安全點的拓展。當線程執行到安全區域時,首先標識自己已進入安全區域,那樣,當在這段時間里 JVM 要發起 GC 時,就不用管標識自己為“安全區域”狀態的線程了,該線程只能乖乖的等待根節點枚舉完或者整個GC過程完成之后才能繼續執行)。
- 有兩種方式可以讓線程在垃圾回收的時候都跑到最近的安全點呢?
- 安全點:從線程角度看,安全點可以理解成是在代碼執行過程中的一些特殊位置,當線程執行到這些位置的時候,說明虛擬機當前的狀態是安全的。比如:方法調用、循環跳轉、異常跳轉等這些地方才會產生安全點。如果有需要,可以在這個位置暫停,比如發生GC時,需要暫停所有活動線程,但是線程在這個時刻,還沒有執行到一個安全點,所以該線程應該繼續執行,到達下一個安全點的時候暫停,等待 GC 結束。
- OopMap的另外一個更根本的作用是,可以幫助 HotSpot 實現準確式 GC (即使用準確式內存管理,虛擬機可通過OopMap知道內存中某個位置的數據具體是什么類型) 。
- 我們知道,一個線程意味著一個棧,一個棧由多個棧幀組成,一個棧幀對應著一個方法,一個方法里面可能有多個安全點。gc 發生時,程序首先運行到最近的一個安全點停下來,然后更新自己的 OopMap ,記下棧上哪些位置代表著引用。枚舉根節點時,遞歸遍歷每個棧幀的 OopMap ,通過棧中記錄的被引用對象的內存地址,即可找到這些對象( GC Roots )。使用 OopMap 可以避免全棧掃描,加快枚舉根節點的速度。但這并不是它的全部用意。
- OopMap 是做什么的?有什么好處?
- 但是現在很多應用僅僅方法區都有好幾百兆,如果逐個檢查這里面的引用,那么會消耗很多時間:
- 首先,為了保證結果的準確性,GC Roots枚舉時是要在STW的情況下進行的,但是由于 JAVA 應用越來越大,所以也不能逐個檢查每個對象是否為 GC Root,那將消耗大量的時間。一個很自然的想法是,能不能用空間換時間,在某個時候把棧上代表引用的位置全部記錄下來,這樣到真正 GC 的時候就可以直接讀取,而不用再一點一點的掃描了。事實上,大部分主流的虛擬機也正是這么做的,比如 HotSpot ,它使用一種叫做 OopMap 的數據結構來記錄這類信息。
- 根可達算法也是JVM 默認使用的尋找垃圾算法,它的原理就是定義了一系列的根,我們把它稱為 “GC Roots” ,從 “GC Roots” 開始往下進行搜索,走過的路徑我們把它稱為 “引用鏈” ,當一個對象到 "GC Roots"之間沒有任何引用鏈相連時,那么這個對象就可以被當做垃圾回收了。
- 三色標記算法:就是垃圾回收器標記垃圾的時候使用的算法,
- 將對象分為三種顏色
- 白色:沒被 GC 訪問過的對象(被 GC 標記完后還是白色,就代表這個對象是垃圾)
- 黑色:存活的對象
- 灰色:被 GC 訪問過的對象,但是對象引用鏈上至少還有一個引用沒被掃描過
- 在并發標記 的時候 可能會出現 誤標的情況,
- 這里舉兩個例子:
- 1.剛開始標記為 垃圾 的對象,但是在并發標記過程中 變為了存活對象
- 第一種情況影響還不算很大,只是相當于垃圾沒有清理干凈,待下一次清理的時候再清理一下就好了
- 2.剛開始標記為 存活 的對象,但是在并發標記過程中 變為了垃圾對象
- 第二種情況就危險了,正在使 用的對象的突然被清理掉 了,后果會很嚴重。那么 產生上述第二種情況的原因是什么呢?當下面這兩種情況 都滿足 的時候就會出現這種問題了
- 1.新增一條或多條 黑色到白色對象的新引用
- 2.刪除 了灰色 對象到該白色對象 的直接引用或間接引用。
- 第二種情況就危險了,正在使 用的對象的突然被清理掉 了,后果會很嚴重。那么 產生上述第二種情況的原因是什么呢?當下面這兩種情況 都滿足 的時候就會出現這種問題了
- 1.剛開始標記為 垃圾 的對象,但是在并發標記過程中 變為了存活對象
- 所以為了解決這個問題,引入了 增量更新 (Incremental Update)和 原始快照 (SATB)的方案:
- 增量更新破壞了第一個條件:增加新引用時記錄 該引用信息,在后續 STW 掃描中重新掃描(CMS的使用方案)。
- 原始快照破壞了第二個條件:刪除引用時記錄下來,在后續 STW 掃描時將這些記錄過的灰色對象為根再掃描一次(G1的使用方案)。
- 這里舉兩個例子:
- 將對象分為三種顏色
- 但一個對象滿足上述條件的時候,不會馬上被回收,也就是說 就算一個對象不可達也并非是非死不可的,暫時處于緩刑狀態,要宣告這個對象死亡至少要經歷兩次標記過程;
- 第一次標記:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那么這個對象 將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。-----書的作者有這么一句話“建議大家完全忘掉Java中的finalize()方法的存在”
- 若對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用過,此時虛擬機認為此對象沒有必要執行finalize()方法(也就是判斷當前對象是否有finalize()方法并且該方法沒有被執行過)
- 若這個對象被判定為有必要執行finalize()方法,那么會將當前對象放入F-Queue隊列中,等待第二次小規模標記,并在稍后由一個虛擬機自動建立的、低優先級的finalize線程去執行(這個執行是指虛擬機會觸發這個finalize()方法,虛擬機不保證該方法一定會被執行,原因是:)
- 如果線程中的一個對象執行的很緩慢或進入了死鎖,很有可能會導致F-Queue隊列中其他對象永久處于等待,會導致回收系統的崩潰;
- 另外就是任何一個對象的finalize()方法都只會被系統自動調用一次,當對象面臨下一次回收時就不能再指望這執行finalize()進行自救了
- 如果線程中的一個對象執行的很緩慢或進入了死鎖,很有可能會導致F-Queue隊列中其他對象永久處于等待,會導致回收系統的崩潰;
- 若這個對象被判定為有必要執行finalize()方法,那么會將當前對象放入F-Queue隊列中,等待第二次小規模標記,并在稍后由一個虛擬機自動建立的、低優先級的finalize線程去執行(這個執行是指虛擬機會觸發這個finalize()方法,虛擬機不保證該方法一定會被執行,原因是:)
- 若對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用過,此時虛擬機認為此對象沒有必要執行finalize()方法(也就是判斷當前對象是否有finalize()方法并且該方法沒有被執行過)
- 第二次標記:此時GC會對F-Queue中的對象進行 第二次小規模的標記,如果此時對象要在finalize()方法中拯救自己逃脫死亡命運,只要與引用鏈上的任何一個對象建立上關聯即可(比如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,就可以在第二次標記時使自己被移出即將回收的范圍之中),否則就會被真的回收。
- 第一次標記:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那么這個對象 將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。-----書的作者有這么一句話“建議大家完全忘掉Java中的finalize()方法的存在”
哦對啦,還有我們公司有個新員工,叫小胡,有些對這個方法的補充,可以分享給你這個大老粗看看嘍。亂用有風險哈,使用需謹慎…
好,說完了堆子哥到 區子哥,區子哥是用下面這個方法,不用上面倆方法。
- 方法區(HotSpot虛擬機中的永久代),Java虛擬機規范中說的是方法區或者說永久代的垃圾收集效率很低
- 永久代的垃圾收集主要回收兩部分內容:
- 廢棄常量:舉個例子,比如說一個字符串(字面量)“hhb”已經進入了常量池中 但是沒有任何一個String對象或者其他地方引用這個常量池中的"hhb"常量,如果此時發生內存回收必要時這個"hhb"就會被系統清理出常量池。常量池中的其他類、接口、方法、字段的符號引用等是類似的
- 無用的類,
- 類同時要滿足下面三個條件這個類才能算是無用的類:【虛擬機可以對滿足下述 3 個條件的無用類進行回收,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收】
- 該類所有的實例都已經被回收(Java堆中不存在該類的任何實例)
- 加載該類的ClassLoader已經被回收
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問到該類的方法們
- 但是不是說滿足了就被回收,是否對類進行回收,
- 類同時要滿足下面三個條件這個類才能算是無用的類:【虛擬機可以對滿足下述 3 個條件的無用類進行回收,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收】
- 永久代的垃圾收集主要回收兩部分內容:
老規矩,買一個圖贈送一個圖。
無論是引用計數還是根可達算法,判斷一個對象是否為垃圾都要和引用掛鉤,所以,咱們看看引用到底都有些啥:
- java 四種引用類型:強引用、軟引用、弱引用、虛引用是什么,有什么區別:【JDK1.2 之前,Java 中引用的定義很傳統:如果 reference 類型的數據存儲的數值代表的是另一塊內存的起始地址,就稱這塊內存代表一個引用。】【在程序設計中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因為軟引用可以加速 JVM 對垃圾內存的回收速度,可以維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生】
- 強引用,就是在程序代碼中普遍存在的普通的對象引用關系,我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。,如 String s = new String("hhb")
- 只要強引用還存在垃圾回收器就永遠不會回收掉被引用的對象,當 內存空間不足,Java 虛擬機寧愿拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。
- 軟引用,用于維護一些可有可無(有用但是并非必須的對象)的對象(軟引用用來描述那些有用但是沒必要的對象。)。如果內存空間足夠,垃圾回收器就不會回收它,只有在內存不足時或者說將要發生內存溢出異常時,系統則會回收軟引用對象,如果回收了軟引用對象之后仍然沒有足夠的內存,才會拋出內存溢出異常。JDK1.2后提供了SoftReference類來實現軟引用// 軟引用
SoftReference<String> softRef = new SoftReference<String>(str);
- 只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
- 軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,JAVA 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
- 弱引用,相比軟引用來說,要更加無用一些,它擁有更短的生命周期(被弱引用關聯的對象只能生存到下一次垃圾回收發生之前),當 JVM 進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象(垃圾回收器開始工作,會回收掉所有只被弱引用關聯的對象)。JDK1.2后提供了WeakReference 實現類來實現弱引用。在 ThreadLocal中就使用了弱引用來防止內存泄漏。WeakReference<String> weakRef = new WeakReference<String>(str);
- 不過,由于垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。
- 弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中
- 虛引用,也叫幽靈引用或者幻影引用。是一種形同虛設的引用,一個對象無法通過虛引用來取得一個對象實例,在現實場景中用的不是很多,它主要用來跟蹤對象被垃圾回收的活動,也就是為一個對象設置虛引用關聯的唯一目的就是能在這個對象被回收器回收時收到一個系統通知。在現實場景中用的不是很多,它主要用來跟蹤對象被垃圾回收的活動。JDK1.2后提供了PhantomReference實現類來實現虛引用
- 虛引用是最弱的一種引用關系,它的唯一作用是用來作為一種通知。如零拷貝(Zero Copy),開辟了堆外內存,虛引用在這里使用,會將這部分信息存儲到一個隊列中,以便于后續對堆外內存的回收管理。
- 形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。
- 虛引用與軟引用和弱引用的一個區別在于: 虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。
- 強引用,就是在程序代碼中普遍存在的普通的對象引用關系,我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。,如 String s = new String("hhb")
打手:知道啦知道啦,啥時候開掃呢,都快臭了。
保潔組長:要不是你我早開工了。兄得們,整起來…咱們分為五個小組哈…先上A組
A組:就叫做“標清”(標記清除法)
打手心里默念:標清,我還高清呢,給誰一天天在那秀呢…
A組:不給您吹,我這標清,是先把垃圾們給"標記"出來,再把標記出來的垃圾給一個一個干掉。
不和你廢話,直接上干貨。
打手:不是聽說有個啥買一贈一嘛,
保潔組長:肯定有呀,來呀,上貨
- 標記清除法(Mark-Sweep):
- 算法分為標記和清除兩個階段
- 第一步:利用 可達性 去遍歷內存,把存活對象和垃圾對象分別進行標記;
- 第二步:在遍歷一遍,將所有標記的對象回收掉
- 優缺點:
- 效率不行,標記和清除的效率都不高;(隨著對象越來越多,那么所需要消耗的時間就會越來越多)
- 標記和清除后會產生大量的不連續的空間分片,可能會導致之后程序運行的時候需分配大對象而找不到連續分片而不得不觸發一次GC;(標記清除后會導致碎片化,如果有大對象分配很有可能分配不下而出發另一次的垃圾收集動作)
- 算法分為標記和清除兩個階段
保潔組長:咳咳咳,咱們時間有限哈,上B組
B組:我們biao整~
打手:不要爭,啥不要爭。
B組:我們會再巡視一遍,會把不是垃圾的正派物件放在一塊,no散放
打手:可以呀,還知道物以類聚,人以群分
別說話,買一贈一
- 標記整理法: 這種算法可以說是結合了標記清除和復制兩種算法,既有標記刪除,又有整理功能
- 第一步:利用 可達性 去遍歷內存,把存活對象和垃圾對象進行標記;
- 第二步:將所有的存活的對象向一段移動,將端邊界以外的對象都回收掉;
- 特點:
- 適用于存活對象多,垃圾少的情況;
- 需要整理的過程,無空間碎片產生;這種算法就是通過標記清除算法找到存活的對象,然后將所有存活的對象,向空間的一端移動,然后回收掉其他的內存。
保潔隊長:C組,趕緊上,快下班了都,抓點緊
C組,我們主要把我們所有的地盤平分為兩塊,一塊用時另一塊閑置,等把用的這一塊用完了,就把這一塊上的好東西放到閑置的那一塊上,然后把之前用的這一塊里面的臟垃圾處理一下。然后,換角色…
打手:啥
C組:算了,大老粗,直接給你上圖吧
老規矩,不偏心,買一贈一
- 復制算法(為了解決標記清除的效率問題): 將 內存按照容量大小分為大小相等的兩塊,每次只使用一塊(對象只在其中一塊區域內分配),當一塊使用完了,就將還存活的對象移到另一塊上,然后在把使用過的內存空間一次性清理掉;
- 現在的商業虛擬機都采用 復制算法來回收新生代,但是新生代中的對象98%都是朝生夕死的所以 不用按照1:1來平分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間(HotSpot默認Eden和Survivor大小比例是8:1),每次使用Eden和其中一塊Survivor
- 回收時將Eden和Survivor中還存活的對象一次性都復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間
- 當Survivor內存不夠時需要依賴其他內存(老年代)進行分配擔保,這些放不下的對象會直接通過分配擔保機制進入老年代
- 回收時將Eden和Survivor中還存活的對象一次性都復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間
- 優缺點:
- 優點:可以看到回收后的對象是排列有序的,這種操作只需要移動指針就可以完成,效率很高
- 缺點:
- 不會產生空間碎片;
- 內存使用率極低,浪費過多的內存,使現有的可用空間變為原先的一半
- 現在的商業虛擬機都采用 復制算法來回收新生代,但是新生代中的對象98%都是朝生夕死的所以 不用按照1:1來平分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間(HotSpot默認Eden和Survivor大小比例是8:1),每次使用Eden和其中一塊Survivor
保潔隊長:D組上,上完吃飯
D組:我們是那啥,分代回收滴
打手:分代,爺爺、孫子…
保潔隊長,nonono,大老粗,算了,給你上圖吧,一看便知
- 分代收集算法: 根據內存對象的存活周期不同,將內存劃分成幾塊,java虛擬機一般將Java堆內存分成新生代和老生代,
- 在新生代中,有大量對象死去和少量對象存活,所以采用復制算法,只需要付出少量存活對象的復制成本就可以完成收集;
- 老年代中因為對象的存活率極高,沒有額外的空間對他進行分配擔保,所以采用標記清理或者標記整理算法進行回收;
- 分代收集理論:大多數的垃圾回收器都遵循了分代收集的理論進行設計,它建立在兩個分代假說之上:這兩種假說的設計原則都是相同的:垃圾收集器應該將jvm劃分出不同的區域,把那些較難回收的對象放在一起(一般指老年代),這個區域的垃圾回收頻率就可以降低,減少垃圾回收的開銷。剩下的區域(一般指新生代)可以用較高的頻率去回收,并且只需要去關心那些存活的對象,也不用標記出需要回收的垃圾,這樣就能夠以較低的代價去完成垃圾回收。
- 兩個分代假說:
- 弱分代假說:絕大多數對象都是朝生夕滅的。
- 強分代假說:熬過越多次數垃圾回收過程的對象就越難消亡。
- 跨代引用假說:如果某個新生代的對象存在了跨代引用,但是老年代的對象是很難消亡的,那么隨著時間的推移,這個新生代對象也會慢慢晉升為老年代對象,那么這種跨代引用也就被消除了。
- 由于跨代引用是很少的,所以我們不應該為了少量的跨代引用去掃描整個老年代的數據,只需要在新生代對象建立一個記憶集來記錄引用信息。
- 記憶集:將老年代分為若干個小塊,每塊區域中有 N 個對象,在對象引用信息發生變動的時候來維護記憶集數據的準確性,這樣每次發生了 “Minor GC” 的時候只需要將記憶集中的對象添加到 "GC Roots"中就可以了。
- 由于跨代引用是很少的,所以我們不應該為了少量的跨代引用去掃描整個老年代的數據,只需要在新生代對象建立一個記憶集來記錄引用信息。
- 兩個分代假說:
嗖…嗖…嗖…
打手:哎呦,還來個這,我倒要看看你啥贈品,剛轉過頭
保潔員和四個組的組員已經撒腿像一樓大門跑去了
保潔組長邊跑邊說:我們餓了,明天再說
打手喊道,哎,你四四組就光說了個咋掃垃圾,還沒說:
- 你們四個組都用的啥工具呀,我咋給你們準備道具呢?
- 或者,你也沒說你們哪個組掃哪里呢?,
哎,你快回來,我已經忍受不住…
打手:算了,我也餓了,先吃飯再說吧,明在和他們細說。
巨人的肩膀:
深入理解Java虛擬機
總結
以上是生活随笔為你收集整理的java基础巩固-宇宙第一AiYWM:为了维持生计,JVM_Part4~(4种垃圾收集算法(标清、标整、复制、分代)、判断是否是垃圾(引用计数、根可达算法))、四种引用类型、整起的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2017-2018-2 20179215
- 下一篇: 水桶理论——联想国际化的奇特哲学