Java記憶體回收機制
一、前言
1.JVM的記憶體結構

jvm2.png
JVM的記憶體結構包括五大區域:程式計數器、虛擬機器棧、本地方法棧、堆區、方法區。其中程式計數器、虛擬機器棧、本地方法棧3個區域隨執行緒而生、隨執行緒而滅,因此這幾個區域的記憶體分配和回收都具備確定性,就不需要過多考慮回收的問題,因為方法結束或者執行緒結束時,記憶體自然就跟隨著回收了。而Java堆區和方法區則不一樣,這部分記憶體的分配和回收是動態的,正是垃圾收集器所需關注的部分。
GC( Garbage Collection)垃圾收集器在對堆區和方法區進行回收前,首先需要考慮的就是如何去判斷哪些垃圾(記憶體)需要回收,這就要用到判斷物件是否存活的演算法!
2.記憶體分配與回收策略
下圖所示是堆中記憶體分配示意圖,建立一個物件,首先會在eden區域分配區域,如果記憶體不夠,就會將年齡大的轉移到Survivor區,當survivor區域儲存不下,則會轉移年老代的。對於一些靜態變數不需要使用物件,直接呼叫的,則會被放入永生代(在Java8中去掉了永久代,以元資料空間代替)。一般來說長期存活的物件最終會被存放到年老代,還有一種特殊情況也會被存放到年老代,就是建立大物件時,比如資料這種需要申請連續空間的,如果空間比較大的,則會直接進入年老代。

image.png
在回收過程中,有一個引數比較重要,就是物件的年齡,如果在一次垃圾回收過程中有使用該物件的,則將物件年齡加1,否則減1,當計數為0,則進行回收,如果年齡達到一定數字則進入老生代。總的來說記憶體分配機制主要體現在物件建立之後是否仍在使用,已經不使用的則回收,繼續使用的則對其年齡進行更新,達到一定程度,轉移到年老代。
二、物件存活判定演算法
1. 引用計數演算法
- 演算法思想:給物件中新增一個引用計數器,每當有一個地方引用它時,計數值加1,當引用失效時,計數器值減1,當引用數為0的時候也就說明這個物件不在被使用就可以被回收。
- 優點:實現簡單,判斷效率也很高。
- 缺點:無法檢測出迴圈引用。如父物件有一個對子物件的引用,子物件反過來引用父物件。這樣,他們的引用計數永遠不可能為0。那麼GC也就無法回收他們。
public class ReferenceCountingGC { public Object instance = null; public static void main(String []args){ // 第一部分 ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); // 第二部分 objA.instance = objB; objB.instance = objA; // 第三部分 objA = null; objB = null; } } 第一部分程式碼,開闢兩塊記憶體,對應的例項我們暫且命名為A和B,分別被objA和objB引用,因此A、B兩個物件的引用計數器分別為1; 第二部分程式碼,由於又有各自的instance指向記憶體A和B,因此A、B兩個物件的引用計數器分別為2; 第三部分程式碼,由於給objA和objB制空,因此A、B兩個物件的引用計數器分別減1,最後A、B兩個物件的引用計數器還是為1,不為0,因此不能回收。
2. 可達性分析演算法
-
演算法思想:通過一系列的“GC Roots”物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的,可以進行記憶體回收。如下圖,Object5、6、7是可以被回收的。
image.png
拓展:
2.1 大多數垃圾回收演算法使用了根集(root set)這個概念;所謂根集就是正在執行的Java程式可以訪問的引用變數的集合(包括區域性變數、引數、類變數),程式可以使用引用變數訪問物件的屬性和呼叫物件的方法。
2.2 在Java語言中,可作為GC Roots的物件包括下面幾種:
- 虛擬機器棧中引用的物件(棧幀中的本地變量表);
- 方法區中類靜態屬性引用的物件;
- 方法區中常量引用的物件;
- 本地方法棧中JNI(Native方法)引用的物件。
其實對於可達性分析演算法,並不是發生一次GC就會對不在GC Roots相連線的引用鏈的物件進行回收,有些物件可能處於 “死緩” 狀態,對於這些物件至少要經歷兩次標記過程。
第一次標記:如果物件在進行可達性分析後發現沒有在引用鏈上,它將會被第一次標記
第二次標記:第一次標記後接著會進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法。篩選的條件是此物件是否有必要執行finalize方法,有必要執行finalize方法的物件就屬於 “死緩” 狀態的物件。
那麼如何判斷是否有必要執行finalize方法呢?
當物件沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機器呼叫過一次,這兩種情況只要滿足其一,都表示沒必要執行finalize()方法。
對於被判定有必要執行finalize()方法的物件,GC會將該物件放到一個叫F-Queue的佇列之中,並由虛擬機器自動建立的一個Finalizer執行緒去執行各個物件的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()...."); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String []args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGC(); SAVE_HOOK = null;// 第一次制空 System.gc(); Thread.sleep(1000); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no, i am dead ..."); } SAVE_HOOK = null;// 第二次制空 System.gc(); Thread.sleep(1000); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no, i am dead ..."); } } } // 輸出如下: finalize().... yes, i am still alive ... no, i am dead ...
通過上述程式碼,可以看到,第一次制空時,執行了finalize方法,由於發生了物件的引用,造成該物件不能進行回收,這樣就發生了物件的自我救贖,SAVE_HOOK並不為null,當第二次制空過後,由於不會再執行finalize方法,因此該物件接下來將會被回收。
三、如何回收?常用的垃圾回收演算法
1. 標記-清除演算法
標記-清除(Mark-Sweep)演算法可以算是最基礎的垃圾收集演算法, 該演算法主要分為“標記”和“清除”兩個階段。 先標記可以被清除的物件,然後統一回收被標記要清除的物件,這個標記過程採用的就是可達性分析演算法。

image.png
主要不足有兩個:
- 一個是效率問題, 標記 和 清除 兩個過程的效率都不高;
- 一個是空間問題,標記清除之後會產生大量不連續的 記憶體碎片 ,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
2. 複製演算法
為了解決效率問題,一種稱為“複製”(Copying)的收集演算法出現了,它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況,只要移動堆頂指標,按順序分配記憶體即可,實現簡單,執行高效。
只是這種演算法的代價是將記憶體縮小為了原來的一半,未免太高了一點。複製演算法的執行過程如下圖所示:

image.png
優點:
- 如果系統中的 垃圾物件很多 ,複製演算法需要複製的存活物件就會相對較少。因此,在真正需要垃圾回收的時刻,複製演算法的 效率是很高 的。
- 回收的記憶體空間沒有碎片。
- 適用於新生代。
缺點
- 將系統 記憶體空間折半 ,只使用一半空間。
- 如果記憶體空間中 垃圾物件少 的話,複製物件也是很 耗時 的,因此,單純的複製演算法也是不可取的。
- 不適合老年代。
3. 標記-整理演算法
標記過程仍然與“標記-清除”演算法一樣,但後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。
複製演算法的高校建立在存活物件少,垃圾物件多的前提下。這種情況在年輕代比較容易發生,在老年代更常見的情況是大部分都是存活物件。
標記整理演算法,是一種老年代的回收演算法,從根節點對所有的物件做一次標記,然後將所有存活的物件移動到記憶體的另外一端,在清除界邊以外所有的空間。這種方法不產生碎片,又不需要2塊相同的記憶體空間。

image.png
4. 分代收集演算法
當前商業虛擬機器的垃圾回收都是採用“分代收集”(Generational Collection)演算法,這種演算法其實並沒有什麼新的思想,只是根據物件的存活週期的不同將記憶體劃分為幾塊。 一般把Java堆分為新生代和老年代 ,然後根據不同年代的特點採用適當的收集演算法。例如在新生代,一般情況下每次垃圾收集都會有大量的物件死去,只有少量的存活,這時候一般使用複製演算法,而對於年老代,由於物件存活率高,沒有額外空間對它跟配擔保,因此一般採用“標記-清理”或“標記-整理”演算法來實現。

image.png
現在大部分虛擬機器基本都是採用分代收集演算法來實現垃圾回收,而分代收集其實又是採用其他三種實現方式結合而成。
四、 GC收集器
垃圾回收器就是對垃圾收集演算法的具體實現。
下圖展示了JDK1.7Update 14之後的HotSpot虛擬機器所包含的收集器。如果兩個收集器之間存在連線,就說明它們可以搭配使用。虛擬機器所處的區域,則表示它是屬於新生代收集器還是老年代收集器。

jvm4.png
4.1 Serial收集器
Serial收集器是一款年輕代的垃圾收集器,使用標記-複製垃圾收集演算法。它是一款發展歷史最悠久的垃圾收集器。 Serial收集器只能使用一條執行緒進行垃圾收集工作,並且在進行垃圾收集的時候,所有的工作執行緒都需要停止工作,等待垃圾收集執行緒完成以後,其他執行緒才可以繼續工作。

JVM5.png
Client模式下的預設收集器,因為沒有執行緒互動的開銷,更能專心的做垃圾收集工作,比其他收集器的單執行緒收集器效果高效。
4.2 ParNew收集器
ParNew收集器其實就是Serial收集器的多執行緒版本,除了使用多執行緒進行垃圾收集之外,其餘行為包括Serial收集器可用的所有控制引數、收集演算法、Stop The Worl、物件分配規則、回收策略等都與Serial 收集器完全一樣.

jvm6.png
ParNew收集器是許多執行在Server模式下的虛擬機器中首選新生代收集器,其中有一個與效能無關但很重要的原因是,除Serial收集器之外,目前只有ParNew它能與CMS收集器配合工作。
4.3 Parallel Scavenge收集器
Parallel Scavenge收集器是一個新生代收集器,它也是使用複製演算法的收集器,又是並行的多執行緒收集器。
Parallel Scavenge收集器關注的是如何控制系統執行的吞吐量(Throughput)。這裡說的吞吐量,指的是CPU用於執行應用程式的時間和CPU總時間的佔比, 吞吐量 = 程式碼執行時間 / (程式碼執行時間 + 垃圾收集時間)。 如果虛擬機器執行的總的CPU時間是100分鐘,而用於執行垃圾收集的時間為1分鐘,那麼吞吐量就是99%
。
在使用者介面程式中,使用低延遲的垃圾收集器會有很好的效果,而對於後臺計算任務的系統,高吞吐量的收集器才是首選。
Parallel Scavenge收集器提供了兩個引數用於控制吞吐量。-XX:MaxGCPauseMillis用於控制最大垃圾收集停頓時間,-XX:GCTimeRatio用於直接控制吞吐量的大小。MaxGCPauseMillis引數的值允許是一個大於0的整數,表示毫秒數,收集器會盡可能的保證每次垃圾收集耗費的時間不超過這個設定值。但是如果這個這個值設定的過小,那麼Parallel Scavenge收集器為了保證每次垃圾收集的時間不超過這個限定值,會導致垃圾收集的次數增加和增加年輕代的空間大小,垃圾收集的吞吐量也會隨之下降。GCTimeRatio這個引數的值應該是一個0-100之間的整數,表示應用程式執行時間和垃圾收集時間的比值。如果把值設定為19,即系統執行時間 : GC收集時間 = 19 : 1,那麼GC收集時間就佔用了總時間的5%(1 / (19 + 1) = 5%),該引數的預設值為99,即最大允許1%(1 / (1 + 99) = 1%)的垃圾收集時間。
Parallel Scavenge收集器還有一個引數:-XX:UseAdaptiveSizePolicy。這是一個開關引數,當開啟這個引數以後,就不需要手動指定新生代的記憶體大小(-Xmn)、Eden區和Survivor區的比值(-XX:SurvivorRatio)以及晉升到老年代的物件的大小(-XX:PretenureSizeThreshold)等引數了, 虛擬機器會根據當前系統的執行情況動態調整合適的設定值來達到合適的停頓時間和合適的吞吐量, 這種方式稱為 GC自適應調節策略。
Parallel Scavenge收集器也是一款多執行緒收集器,但是由於目的是為了控制系統的吞吐量,所以這款收集器也被稱為 吞吐量優先收集器。
4.4 Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣是一個單執行緒收集器,使用標記整理演算法。這個收集器的主要意義也是在於給Client模式下的虛擬機器使用。
如果在Server模式下,主要兩大用途:
(1)在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用
(2)作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用

jvm7.png
4.5 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"標記-整理"演算法。這個收集器是在JDK1.6版本中出現的。
在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old這款單執行緒的老年代收集器配合使用。
Parallel Old垃圾收集器和Parallel Scavenge收集器一樣,也是一款關注吞吐量的垃圾收集器,和Parallel Scavenge收集器一起配合,可以實現對Java堆記憶體的吞吐量優先的垃圾收集策略。
Parallel Old垃圾收集器的工作原理和Parallel Scavenge收集器類似。

jvm8.png
4.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在網際網路站或者B/S系統的服務端上,這類應用尤其重視伺服器的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗。CMS收集器就非常符合這類應用的需求。

jvm10.png
從圖中可以看出,CMS收集器的工作過程可以分為4個階段:
- 初始標記(CMS initial mark)階段
- 併發標記(CMS concurrent mark)階段
- 重新標記(CMS remark)階段
- 併發清除(CMS concurrent sweep)階段
從圖中可以看出,在這4個階段中,
- 初始標記和重新標記這兩個階段都是隻有GC執行緒在執行,使用者執行緒會被停止,所以這兩個階段會發送STW(Stop The World)。
- 初始標記階段的工作是標記GC Roots可以直接關聯到的物件,速度很快。
- 併發標記階段,會從GC Roots 出發,標記處所有可達的物件,這個過程可能會花費相對比較長的時間,但是由於在這個階段,GC執行緒和使用者執行緒是可以一起執行的,所以即使標記過程比較耗時,也不會影響到系統的執行。
- 重新標記階段,是對併發標記期間因使用者程式執行而導致標記變動的那部分記錄進行修正,重新標記階段耗時一般比初始標記稍長,但是遠小於併發標記階段。
- 最終,會進行併發清理階段,和併發標記階段類似,併發清理階段不會停止系統的執行,所以即使相對耗時,也不會對系統執行產生大的影響。
由於併發標記和併發清理階段是和應用系統一起執行的,而初始標記和重新標記相對來說耗時很短,所以可以認為CMS收集器在執行過程中,是和應用程式是併發執行的。由於CMS收集器是一款併發收集和低停頓的垃圾收集器,所以CMS收集器也被稱為 併發低停頓收集器。
雖然CMS收集器可以是實現低延遲併發收集,但是也存在一些不足。
- 首先,CMS收集器對CPU資源非常敏感。 對於併發實現的收集器而言,雖然可以利用多核優勢提高垃圾收集的效率,但是由於收集器在執行過程中會佔用一部分的執行緒,這些執行緒會佔用CPU資源,所以會影響到應用系統的執行,會導致系統總的吞吐量降低。CMS預設開始的回收執行緒數是(Ncpu + 3) / 4,其中Ncpu是機器的CPU數。所以,當機器的CPU數量為4個以上的時候,垃圾回收執行緒將佔用不少於%25的CPU資源,並且隨著CPU數量的增加,垃圾回收執行緒佔用的CPU資源會減少。但是,當CPU資源少於4個的時候,垃圾回收執行緒佔用的CPU資源的比例會增大,會影響到系統的執行,假設有2個CPU的情況下,垃圾回收執行緒將會佔據超過50%的CPU資源。所以,在選用CMS收集器的時候,需要考慮,當前的應用系統,是否對CPU資源敏感。
- 其次,CMS收集器在處理垃圾收集的過程中,可能會產生浮動垃圾,由於它無法處理浮動垃圾,所以可能會出現Concurrent Mode Failure問題而導致觸發一次Full GC。 所謂的浮動垃圾,是由於CMS收集器的併發清理階段,清理執行緒是和使用者執行緒一起執行,如果在清理過程中,使用者執行緒產生了垃圾物件,由於過了標記階段,所以這些垃圾物件就成為了浮動垃圾,CMS無法在當前垃圾收集過程中集中處理這些垃圾物件。由於這個原因,CMS收集器不能像其他收集器那樣等到完全填滿了老年代以後才進行垃圾收集,需要預留一部分空間來保證當出現浮動垃圾的時候可以有空間存放這些垃圾物件。在JDK 1.5中,預設當老年代使用了68%的時候會啟用垃圾收集,這是一個保守的設定,如果在應用中老年代增長不是很快,可以通過引數" -XX:CMSInitiatingOccupancyFraction "控制觸發的百分比,以便降低記憶體回收次數來提供效能。在JDK 1.6中,CMS收集器的啟用閥值變成了92%。如果在CMS執行期間沒有足夠的記憶體來存放浮動垃圾,那麼就會導致"Concurrent Mode Failure"失敗,這個時候,虛擬機器將啟動後備預案,臨時啟動Serial Old收集器來對老年代重新進行垃圾收集,這樣會導致垃圾收集的時間邊長,特別是當老年代記憶體很大的時候。所以對引數" -XX:CMSInitiatingOccupancyFraction "的設定,過高,會導致發生Concurrent Mode Failure,過低,則浪費記憶體空間。
- CMS的最後一個問題,就是它在進行垃圾收集時使用的"標記-清除"演算法,在進行垃圾清理以後,會出現很多記憶體碎片。 過多的記憶體碎片會影響大物件的分配,會導致即使老年代記憶體還有很多空閒,但是由於過多的記憶體碎片,不得不提前觸發垃圾回收。為了解決這個問題,CMS收集器提供了一個" -XX:+UseCMSCompactAtFullCollection "引數,用於CMS收集器在必要的時候對記憶體碎片進行壓縮整理。由於記憶體碎片整理過程不是併發的,所以會導致停頓時間變長。" -XX:+UseCMSCompactAtFullCollection "引數預設是開啟的。虛擬機器還提供了一個" -XX:CMSFullGCsBeforeCompaction "引數,來控制進行過多少次不壓縮的Full GC以後,進行一次帶壓縮的Full GC,預設值是0,表示每次在進行Full GC前都進行碎片整理。
雖然CMS收集器存在上面提到的這些問題,但是毫無疑問,CMS當前仍然是非常優秀的垃圾收集器。
4.7 G1收集器
G1收集器是當今收集器技術最前沿的成功之一與其他。與其他收集器相比主要特點如下:
1、並行與併發:G1能充分的利用多CPU、多核的環境使用多個CPU來縮短停頓的時間,也就是說同樣擁有和使用者執行緒同時執行的功能。
2、分代收集:雖然G1可以不需要其他收集器的配合就能獨立管理整個Java堆,但是還是採用了不同的方式去處理新建物件和存活了一短時間的物件,這樣效果更佳
3、空間整理:與CMS的標記清理演算法不同,G1從整體來看是基於標記整理演算法實現的,從區域性兩個Region上來看是基於複製演算法,但是不管哪種演算法都不會產生記憶體碎片的問題。
4、可預測的停頓時間:這是G1比CMS的另一優勢,降低停頓時間是CMS和G1的共同關注點,但是G1出了追求低停頓外,還可以預測停頓的時間,讓使用者明確指定一個長度為毫秒的時間,消耗在垃圾收集的時間不超過這個時間。
G1收集器不再是完全的將堆劃分新生代和老年代,取而代之的是將堆劃分為多個大小的相等的獨立區域(Region),雖然還保留了新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。
G1可以預測停頓時間是因為它可以有計劃的避免對整個Java堆進行全區域的垃圾收集,G1跟蹤各個Region裡面的垃圾價值情況,也就是回收後會獲得的空間大小和回收所需要多少時間的經驗,在後臺維護一個優先列表,每次根據允許的時間去判斷回收哪個區域後獲得的價值更大,這樣使用Region和優先順序的方式回收,可以保證G1在有限的時間內獲得最高的收集價值。
因為一個物件被分配到一個Region中,但是並非只能本Region中的其他物件才能引用,而是可以被整個Java堆中的任意物件所產生引用關係,那麼為了避免進行全域性的掃描,G1收集器在每個Region中都維護了一個Remembered Set(用來記錄跨Region引用的資料結構,在分代中就是記錄誇新生代和老年代)。如果虛擬機發現程式在對Reference型別的資料進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,然後檢查Reference引用的物件是否處於不同的Region之中(在分代中就是檢查老年代和新生代的誇代引用),如果是就會通過CardTable(可以理解為是Remembered Set的一種實現)把相關引用的資訊記錄到被引用物件所屬的Region的Rememered Set之中。當進行記憶體回收時,在GC跟節點的範圍加入對Remembered Set中的物件分析,這樣就不用為了查詢引用而進行全堆的搜尋了
使用G1收集器時,Java堆的記憶體佈局是整個規劃為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region的集合。
G1收集器之所以能建立可預測的停頓時間模型,是因為它可以有計劃地避免在真個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲取的空間大小以及回收所需要的時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的又來)。這種使用Region劃分記憶體空間以及有優先順序的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘量可能高的灰機效率,G1 記憶體“化整為零”的思路在GC根節點的列舉範圍中加入Remembered Set即可保證不對全堆掃描也不會遺漏。
如果不計算Remembered Set的操作,G1收集器的運作大致可劃分為以下幾個步驟:
- 初始標記(Initial Marking) : 初始標記階段只是為了標記一下GC Roots能直接關聯到的物件,並且修改TAMS(Next Top Mark Start)的值,讓下一個階段使用者程式併發執行時,能在正確的Region中建立物件。這個階段需要停頓執行緒,但是耗時很短
- 併發標記(Concurrent Marking): 併發標記階段是從GC Roots開始對堆中的物件進行可達性分析,找出存活的物件,這個階段耗時較長,但是可以和使用者執行緒併發執行。
- 最終標記(Final Marking): 最終標記階段是為了修正併發標記期間程式繼續執行而導致標記產生變化的一部分物件的記錄,虛擬機器將這段時間對物件的變化記錄線上程REmembered Set Logs裡面,最終標記階段需要把Remembered Set Logs的資料合併到Remembered Set中,這個階段需要停頓執行緒,可是可以並行執行。
篩選回收(Live Data Counting and Evacuation) : 篩選回收階段首先要對各個Region的回收價值和成本進行排序,然後根據使用者所期望的GC停頓時間來制定回收計劃,從Sun透露的訊息,這個階段可以做到和使用者執行緒併發執行,但是因為只是回收一部分Region,時間是使用者可控的,而且停頓使用者執行緒將大幅度提高手機的效率。
jvm11.png
4.8 GC收集器總結
- 吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)
-
停頓時間短則響應速度好提升使用者體驗;高吞吐量則CPU利用率高,適合後臺運算
image.png
五、參考
1. https://blog.csdn.net/u010349644/article/details/82191822
2. https://blog.csdn.net/yqlakers/article/details/70138786
3. https://blog.csdn.net/u012998254/article/details/81428621
4. https://blog.csdn.net/wen7280/article/details/54428387
5. https://www.cnblogs.com/chengxuyuanzhilu/p/7088316.html
6. https://blog.csdn.net/yulong0809/article/details/77421615