文章目錄
java
JVM——(1)爲何學習虛擬機
JVM——(2)聊聊JVM虛擬機
JVM——(3)類加載子系統
JVM——(4)運行時數據區的概述與程序計數器(PC寄存器)
JVM——(5)運行時數據區的虛擬機棧
JVM——(6)運行時數據區的本地方法棧
JVM——(7)運行時數據區的堆空間
JVM——(8)運行時數據區的方法區
JVM——(9)對象的實例化與訪問定位
JVM——(10)執行引擎
JVM——(11)String Table(字符串常量池)
JVM——(12)垃圾回收概述
JVM——(13)垃圾回收相關算法
JVM——(14)垃圾回收相關概念的概述
JVM——(15)垃圾回收器詳細篇
JVM——(16)Class文件結構一(描述介紹)
JVM——(17)Class文件結構二(解讀字節碼)
JVM——(18)Class文件結構三(JAVAP指令)
JVM——(19)字節碼指令集與解析一(局部變量壓棧、常量變量壓棧、出棧局部變量表指令)
JVM——(20)字節碼指令集與解析二(算數指令)
JVM——(21)字節碼指令集與解析三(類型轉換指令)
JVM——(22)字節碼指令集與解析四(對象建立與訪問指令)
JVM——(23)字節碼指令集與解析五(方法調用指令與方法返回指令)
JVM——(24)字節碼指令集與解析六(操做數棧管理指令)算法
上篇咱們講的是垃圾回收的概述,那麼從本篇開始咱們說的是垃圾回收的相關算法數組
這些算法咱們並不會手動的實現,但咱們須要知道原理是什麼,背景是什麼,應用場景有什麼緩存
咱們沒有必要底層去實現,而且自己算法也是很複雜要考慮的細節有不少併發
咱們一提到垃圾回收的算法GC,那麼其實翻譯成兩個詞:垃圾回收器、垃圾回收ide
那麼咱們提到GC就是要垃圾回收,那咱們須要知道哪些是垃圾結構(對象),將它清理函數
那麼怎麼找到這些垃圾呢?找到以後呢怎麼清理回收呢?工具
因此咱們把GC的分紅二個階段:標記階段、清楚階段性能
標記階段:識別垃圾結構(對象)識別出來。涉及到算法有:引用計數算法、可達性分析算法學習
清楚階段:釋放垃圾對象所佔用的內存空間。涉及到算法有:標記-清除算法(Mark-Sweep)、複製算法(Copying)、標記-壓縮算法(Mark-Compact)
================================
在堆裏存放着幾乎全部的Java對象實例在GC執行垃圾回收以前,首先須要區分出內存中哪些是存活對象,哪些是已經死亡的對象。
只有被標記爲己經死亡的對象,GC纔會在執行垃圾回收時,釋放掉其所佔用的內存空間,所以這個過程咱們能夠稱爲垃圾標記階段
那麼在JVM中到底是如何標記一個死亡對象呢?簡單來講當一個對象已經再也不被任何的存活對象繼續引用時,就能夠宣判爲已經死亡
判斷對象存活通常有兩種方式:引用計數算法和可達性分析算法
================================
引用計數算法(Reference Counting)比較簡單,對每一個對象保存一個整型的引用計數器屬性。用於記錄對象被引用的狀況
對於一個對象A,只要有任何一個對象引用了A 則A的引用計數器就加1;當引用失效時引用計數器就減1。只要對象A的引用計數器的值爲0,即表示對象A不可能再被使用,可進行回收
================================
當p的指針斷開的時候內部的引用造成一個循環,計數器都還算1,沒法被回收,這就是循環引用從而形成內存泄漏
接下來咱們使用示例代碼來看看Java有沒有使用這個引用計數算法?
public class RefCountGC { //這個成員屬性惟一的做用就是佔用一點內存 private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB Object reference = null; public static void main(String[] args) { RefCountGC obj1 = new RefCountGC(); RefCountGC obj2 = new RefCountGC(); obj1.reference = obj2; obj2.reference = obj1; obj1 = null; obj2 = null; }}
當咱們不當心直接把obj1.reference和obj2.reference置爲null。
若咱們使用引用計數算法那麼則在Java堆中的兩塊內存依然保持着互相引用,沒法被回收
具體是否是這樣呢?咱們運行起來先看看堆空間的大小
這時咱們採用如下的代碼塊,運行起來看看是否會被GC回收
public class RefCountGC { //這個成員屬性惟一的做用就是佔用一點內存 private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB Object reference = null; public static void main(String[] args) { RefCountGC obj1 = new RefCountGC(); RefCountGC obj2 = new RefCountGC(); obj1.reference = obj2; obj2.reference = obj1; obj1 = null; obj2 = null; //顯式的執行垃圾回收行爲 //這裏發生GC,obj1和obj2可否被回收? System.gc(); }}
若咱們這時開啓GC,與上面沒有開啓GC的堆空間大小是同樣的,那就未被回收
對比未開啓前的堆空間,咱們開啓後的堆空間大小。這就說明他們兩被回收了
================================
引用計數算法,是不少語言的資源回收選擇,例如因人工智能而更加火熱的Python,它更是同時支持引用計數和垃圾收集機制
具體哪一種最優是要看場景的,業界有大規模實踐中僅保留引用計數機制,以提升吞吐量的嘗試
Java並無選擇引用計數,是由於其存在一個基本的難題,也就是很難處理循環引用關係
可達性分析算法:也能夠稱爲根搜索算法、追蹤性垃圾收集
相對於引用計數算法而言,可達性分析算法不只一樣具有實現簡單和執行高效等特色,更重要的是該算法能夠有效地解決在引用計數算法中循環引用的問題,防止內存泄漏的發生
相較於引用計數算法這裏的可達性分析就是Java、C#選擇的。這種類型的垃圾收集一般也叫做追蹤性垃圾收集(Tracing Garbage Collection)
================================
可達性分析算法是以根對象集合(GCRoots)爲起始點,按照從上至下的方式搜索被根對象集合所鏈接的目標對象是否可達
所謂"GCRoots"根集合就是一組必須活躍的引用
使用可達性分析算法後,內存中的存活對象都會被根對象集合直接或間接鏈接着,搜索所走過的路徑稱爲引用鏈(Reference Chain)
若是目標對象沒有任何引用鏈相連(Object 五、六、7)則是不可達的,就意味着該對象己經死亡,能夠標記爲垃圾對象
在可達性分析算法中,只有可以被根對象集合直接或者間接鏈接的對象(Object 一、二、三、4)纔是存活對象
================================
1.虛擬機棧中引用的對象:好比各個線程被調用的方法中使用到的參數、局部變量等。
2.本地方法棧內JNI(一般說的本地方法)引用的對象
3.方法區中類靜態屬性引用的對象:好比:Java類的引用類型靜態變量
4.方法區中常量引用的對象:好比:字符串常量池(StringTable)裏的引用
5.全部被同步鎖synchronized持有的對象
6.Java虛擬機內部的引用:基本數據類型對應的Class對象、一些常駐的異常對象(如:NullPointerException、OutofMemoryError),系統類加載器
7.反映java虛擬機內部狀況的JMXBean、JVMTI中註冊的回調、本地代碼緩存等
除了這些固定的GC Roots集合之外,根據用戶所選用的垃圾收集器以及當前回收的內存區域不一樣,還能夠有其餘對象「臨時性」地加入,共同構成完整GC Roots集合。好比:分代收集和局部回收(PartialGC)
若是隻針對Java堆中的某一塊區域進行垃圾回收(好比:典型的只針對新生代),必須考慮到內存區域是虛擬機本身的實現細節,更不是孤立封閉的
這個區域的對象徹底有可能被其餘區域的對象所引用,這時候就須要一併將關聯的區域對象也加入GC Roots集合中去考慮,才能保證可達性分析的準確性。
因爲Root採用棧方式存放變量和指針,因此若是一個指針它保存了堆內存裏面的對象,可是本身又不存放在堆內存裏面,那它就是一個Root
可是也有一些須要注意的地方,好比說若是要使用可達性分析算法來判斷內存是否可回收,那麼分析工做必須在一個能保障一致性的快照中進行。
這點不知足的話分析結果的準確性就沒法保證,這點也是致使GC進行時必須「Stop The World」的一個重要緣由。即便是號稱(幾乎)不會發生停頓的CMS收集器中,枚舉根節點時也是必需要停頓的
接下里咱們說關於在回收以前涉及到方法的調用邏輯,也就是Java語言提供了對象終止(finalization)機制來容許開發人員提供對象被銷燬以前的自定義處理邏輯
當垃圾回收器發現沒有引用指向一個對象,咱們稱呼爲垃圾進行回收。即垃圾回收此對象以前,總會先調用這個對象的finalize()方法
咱們能夠看看Object 類的finalize()方法,能夠看到並無被final修飾說明可重寫
finalize() 方法容許在子類中被重寫,用於在對象被回收時進行資源釋放。
建議永遠不要主動調用某個對象的finalize()方法,應該交給垃圾回收機制調用。
理由有下面三點:
從功能上來講finalize()方法與C++中的析構函數比較類似,可是Java採用的是基於垃圾回收器的自動內存管理機制,因此finalize()方法在本質上不一樣於C++中的析構函數
finalize()方法對應了一個finalize線程可是優先級比較低,即便主動調用該方法也不會所以就直接進行回收
因爲finalize()方法的存在,虛擬機中的對象通常處於三種可能的狀態。
若是從全部的根節點都沒法訪問到某個對象,說明對象己經再也不使用了。通常來講此對象須要被回收。但事實上也並不是是「非死不可」的,這時候它們暫時處於「緩刑」階段。
一個沒法觸及的對象有可能在某一個條件下「復活」本身,若是這樣那麼對它當即進行回收就是不合理的。爲此定義虛擬機中的對象可能的三種狀態
不可觸及的對象不可能被複活,由於finalize()只會被調用一次。以上3種狀態中是因爲finalize()方法的存在進行的區分。只有在對象不可觸及時才能夠被回收
================================
斷定一個對象objA是否可回收,至少要經歷兩次標記過程:
若是對象objA沒有重寫finalize()方法,或者finalize()方法已經被虛擬機調用過,則虛擬機視爲「沒有必要執行」,objA被斷定爲不可觸及的
若是對象objA重寫了finalize()方法且還未執行過,那麼objA會被插入到F-Queue隊列中由一個虛擬機自動建立的、低優先級的Finalizer線程觸發其finalize()方法執行
咱們能夠運行上面的那個示例代碼,看看這個Finalizer線程
finalize()方法是對象逃脫死亡的最後機會,稍後GC會對F-Queue隊列中的對象進行第二次標記
若是objA在finalize()方法中與引用鏈上的任何一個對象創建了聯繫,那麼在第二次標記時,objA會被移出「即將回收」集合
若是後該對象會再次出現沒有引用存在的狀況。在這個狀況下finalize()方法則不會被再次調用,對象會直接變成不可觸及的狀態。也就是說一個對象的finalize()方法只會被調用一次。
接下來咱們經過一個示例代碼來演示一下finalize
public class CanReliveObj { //類變量,屬於 GC Root public static CanReliveObj obj; public static void main(String[] args) { try { //建立一個對象 obj = new CanReliveObj(); //本身將本身置空 obj = null; System.gc();//調用垃圾回收器 System.out.println("第1次 gc"); // 若是對象objA到GC Roots沒有引用鏈,則進行第一次標記 // 再次進行篩選,判斷此對象是否有必要執行finalize()方法 // 由於Finalizer線程優先級很低,暫停2秒,以等待它 Thread.sleep(2000); if (obj == null) { System.out.println("obj is dead"); } else { System.out.println("obj is still alive"); } } catch (InterruptedException e) { e.printStackTrace(); } }}//運行結果以下:第1次 gc obj is dead
咱們這裏並無重寫finalize()方法,因此當咱們沒有引用鏈的時候,被判不可觸及了
接下來咱們重寫一下finalize()方法,自我拯救一下看看會判什麼樣呢?
public class CanReliveObj { public static CanReliveObj obj;//類變量,屬於 GC Root //此方法只能被調用一次 @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("調用當前類重寫的finalize()方法"); obj = this;//當前待回收的對象在finalize()方法中與引用鏈上的一個對象obj創建了聯繫 } public static void main(String[] args) { try { obj = new CanReliveObj(); // 對象第一次成功拯救本身 obj = null; System.gc();//調用垃圾回收器 System.out.println("第1次 gc"); Thread.sleep(2000); if (obj == null) { System.out.println("obj is dead"); } else { System.out.println("obj is still alive"); } System.out.println("第2次 gc"); obj = null; System.gc();//調用垃圾回收器 Thread.sleep(2000); if (obj == null) { System.out.println("obj is dead"); } else { System.out.println("obj is still alive"); } } catch (InterruptedException e) { e.printStackTrace(); } }}//運行結果以下:第1次 gc 調用當前類重寫的finalize()方法 obj is still alive 第2次 gc obj is dead
這時咱們就能夠看到沒有引用鏈,則進行第一次標記,可是咱們重寫finalize()方法因此虛擬機進行執行而且復活回來,當咱們第二次斷開引用鏈的時候,就不在執行finalize()方法將它判爲
不可觸及的狀態進行回收
MAT是Memory Analyzer的簡稱,它是一款功能強大的Java堆內存分析器。
用於查找內存泄漏以及查看內存消耗狀況,是基於Eclipse開發的是一款免費的性能分析工具
MAT是基於Eclipse開發的,是一款免費的性能分析工具,可點擊:下載入口
雖然Jvisualvm很強大,可是在內存分析方面,仍是MAT更好用一些
此小節主要是爲了實時分析GC Roots是哪些東西,中間須要用到一個dump的文件
那麼咱們這時演示一下若是使用JVisualVM獲取下面代碼塊的dump文件
public class GCRootsTest { public static void main(String[] args) { List<Object> numList = new ArrayList<>(); Date birth = new Date(); for (int i = 0; i < 100; i++) { numList.add(String.valueOf(i)); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("數據添加完畢,請操做:"); new Scanner(System.in).next(); numList = null; birth = null; System.out.println("numList、birth已置空,請操做:"); new Scanner(System.in).next(); System.out.println("結束"); }}
咱們這個代碼塊numList 和 birth 在第一次捕捉內存快照的時候,爲 GC Roots
以後 numList 和 birth 置爲 null ,對應的引用對象被回收,在第二次捕捉內存快照的時候,就再也不是 GC Roots
那麼此時咱們將代碼運行起來,捕捉第一次內存快照並使用JVisualVM工具查看dump文件
接下來咱們就可使用MAT 打開剛剛另存爲的快照
若是咱們想查看當前快照的GC Roots,能夠按照下圖點擊
固然咱們能夠查看MAT的官網文檔有對GC Root下的相關參數介紹:訪問入口
接下來咱們能夠展開Thread 查看咱們剛剛的主線程
展開咱們的主線程能夠看到咱們定義的兩個局部變量,類型分別爲 ArrayList 和 Date
這時咱們將程序繼續執行,釋放引用鏈再另存爲看看他們的快照
這時咱們再用MAT打開另外一份快照看看釋放引用鏈後的GC Root有哪些
一樣的咱們查看剛剛的主線程,看看以前定義的ArrayList 和 Date 還存在嗎?
咱們當前程序當中才三十幾行的代碼,就出現了1700 GC Roots,如果上千行的話就更多了
因此通常開發當中,查看GC Root沒有那麼多的機會,不必全看
咱們只須要觀看某一個引用變量的這一支 GC Root
接下來咱們能夠針對剛剛程序,使用另一個JProfiler 進行 GC Roots 溯源演示
右擊對象,選擇 Show Selection In Heap Walker,單獨的查看某個對象
咱們查看這個char[] 數組的應用看看,有哪些
點擊Show Paths To GC Roots,在彈出界面中選擇默認設置便可
下面咱們使用一個示例代碼用JProfiler 進行OOM 異常排查
public class HeapOOM { byte[] buffer = new byte[1 * 1024 * 1024];//1MB public static void main(String[] args) { ArrayList<HeapOOM> list = new ArrayList<>(); int count = 0; try{ while(true){ list.add(new HeapOOM()); count++; } }catch (Throwable e){ System.out.println("count = " + count); e.printStackTrace(); } }}
進行堆大小設置,以及出現OOM時再目錄成dump文件
這時咱們運行起來的時候,就會爆出OOM的異常
這時咱們能夠查看剛剛的命令幫咱們生成的dump文件
這時咱們運用JProfiler 打開它,而且查看一下
當成功區分出內存中存活對象和死亡對象後,GC接下來的任務就是執行垃圾回收,釋放掉無用對象所佔用的內存空間,以便有足夠的可用內存空間爲新對象分配內存
目前在JVM中比較常見的三種垃圾收集算法是
================================
標記-清除算法(Mark-Sweep)是一種很是基礎和常見的垃圾收集算法,該算法被J.McCarthy等人在1960年提出並並應用於Lisp語言。
當堆中的有效內存空間(available memory)被耗盡的時候,就會中止整個程序(也被稱爲stop the world),而後進行兩項工做:標記、清除
這裏所謂的清除並非真的置空,而是把須要清除的對象地址保存在空閒的地址列表裏。下次有新對象須要加載時,判斷垃圾的位置空間是否夠,若是夠就存放(也就是覆蓋原有的地址)
關於空閒列表是在爲對象分配內存的時候提過:
標記清除算法的效率不算高在進行GC的時候,須要中止整個應用程序,用戶體驗較差
這種方式清理出來的空閒內存是不連續的,產生內碎片,須要維護一個空閒列表
================================
爲了解決標記-清除算法在垃圾收集效率方面的缺陷
M.L.Minsky於1963年發表了著名的論文,「使用雙存儲區的Lisp語言垃圾收集器CA LISP GarbageCollector Algorithm Using Serial Secondary Storage)」
M.L.Minsky在該論文中描述的算法被人們稱爲複製(Copying)算法,它也被M.L.Minsky本人成功地引入到了Lisp語言的一個實現版本中
將活着的內存空間分爲兩塊,每次只使用其中一塊在垃圾回收時將正在使用的內存中的存活對象複製到未被使用的內存塊中,以後清除正在使用的內存塊中的全部對象,交換兩個內存的角色最後完成垃圾回收
新生代裏面就用到了複製算法,Eden區和S0區存活對象總體複製到S1區
沒有標記和清除過程,實現簡單,運行高效
複製過去之後保證空間的連續性,不會出現「碎片」問題。
此算法的缺點也是很明顯的,就是須要兩倍的內存空間。
對於G1這種分拆成爲大量region的GC,複製而不是移動意味着GC須要維護region之間對象引用關係,無論是內存佔用或者時間開銷也不小
若是系統中的垃圾對象不少,複製算法須要複製的存活對象數量並不會太大,效率較高
老年代大量的對象存活,那麼複製的對象將會有不少,效率會很低
在新生代對常規應用的垃圾回收,一次一般能夠回收70% - 99% 的內存空間。回收性價比很高。因此如今的商業虛擬機都是用這種收集算法回收新生代
================================
複製算法的高效性是創建在存活對象少、垃圾對象多的前提下的。這種狀況在新生代常常發生,可是在老年代更常見的狀況是大部分對象都是存活對象
若是依然使用複製算法因爲存活對象較多,複製的成本也將很高。所以基於老年代垃圾回收的特性,須要使用其餘的算法。
標記-清除算法的確能夠應用在老年代中,可是該算法不只執行效率低下並且在執行完內存回收後還會產生內存碎片,因此JVM的設計者須要在此基礎之上進行改進。標記-壓縮(Mark-Compact)算法由此誕生
1970年先後,G.L.Steele、C.J.Chene和D.s.Wise等研究者發佈標記-壓縮算法。在許多現代的垃圾收集器中人們都使用了標記-壓縮算法或其改進版本
第一階段和標記清除算法同樣,從根節點開始標記全部被引用對象
第二階段將全部的存活對象壓縮到內存的一端,按順序排放。以後,清理邊界外全部的空間。
標記-壓縮算法的最終效果等同於標記-清除算法執行完成後,再進行一次內存碎片整理,所以,也能夠把它稱爲標記-清除-壓縮(Mark-Sweep-Compact)算法
兩者的本質差別在於標記-清除算法是一種非移動式的回收算法,標記-壓縮是移動式的。是否移動回收後的存活對象是一項優缺點並存的風險決策
能夠看到標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可,這比維護一個空閒列表顯然少了許多開銷
消除了標記-清除算法當中,內存區域分散的缺點,咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可
消除了複製算法當中,內存減半的高額代價
從效率上來講,標記-整理算法要低於複製算法
移動對象的同時,若是對象被其餘對象引用,則還須要調整引用的地址(由於HotSpot虛擬機採用的不是句柄池的方式,而是直接指針)
移動過程當中,須要全程暫停用戶應用程序。即:STW
================================
效率上來講,複製算法是當之無愧的老大,可是卻浪費了太多內存
而爲了儘可能兼顧上面提到的三個指標,標記-整理算法相對來講更平滑一些,可是效率上不盡如人意,它比複製算法多了一個標記的階段,比標記-清除多了一個整理內存的階段
================================
難道就沒有一種最優的算法嗎?答:無,沒有最好的算法,只有最合適的算法。具體問題具體分析
前面全部這些算法中並無一種算法能夠徹底替代其餘算法,它們都具備本身獨特的優點和特色。
這時分代收集算法應運而生是基於這樣一個事實:不一樣的對象的生命週期是不同的
所以不一樣生命週期的對象能夠採起不一樣的收集方式,以便提升回收效率,通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色使用不一樣的回收算法,以提升垃圾回收的效率
在Java程序運行的過程當中會產生大量的對象,其中有些對象是與業務信息相關:
Http請求中的Session對象、線程、Socket鏈接這類對象跟業務直接掛鉤,所以生命週期比較長
可是還有一些對象主要是程序運行過程當中生成的臨時變量,這些對象生命週期會比較短
好比:String對象因爲其不變類的特性,系統會產生大量的這些對象有些對象甚至只用一次便可回收
在HotSpot中,基於分代的概念GC所使用的內存回收算法必須結合年輕代和老年代各自的特色
這種狀況複製算法的回收整理速度是最快的,複製算法的效率只和當前存活對象大小有關,所以很適用於年輕代的回收。而複製算法內存利用率不高的問題,經過hotspot中的兩個survivor的設計獲得緩解
這種狀況存在大量存活率高的對象,複製算法明顯變得不合適。通常是由標記-清除或者是標記-清除與標記-整理的混合實現
以HotSpot中的CMS回收器爲例,CMS是基於Mark-Sweep實現的,對於對象的回收效率很高。對於碎片問題CMS採用基於Mark-Compact算法的Serial Old回收器做爲補償措施:當內存回收不佳(碎片致使的Concurrent Mode Failure時),將採用Serial Old執行Full GC以達到對老年代內存的整理
提示:分代的思想被現有的虛擬機普遍使用。幾乎全部的垃圾回收器都區分新生代和老年代
================================
上述現有的算法,在垃圾回收過程當中,應用軟件將處於一種Stop the World的狀態
在Stop the World狀態下應用程序全部的線程都會掛起暫停一切正常的工做等待垃圾回收的完成
若是垃圾回收時間過長應用程序會被掛起好久,將嚴重影響用戶體驗或者系統的穩定性。
爲了解決這個問題,即對實時垃圾收集算法的研究直接致使了增量收集(Incremental Collecting)算法的誕生
若是一次性將全部的垃圾進行處理,須要形成系統長時間的停頓,那麼就可讓垃圾收集線程和應用程序線程交替執行。每次垃圾收集線程只收集一小片區域的內存空間,接着切換到應用程序線程。依次反覆,直到垃圾收集完成
總的來講,增量收集算法的基礎還是傳統的標記-清除和複製算法。增量收集算法經過對線程間衝突的妥善處理,容許垃圾收集線程以分階段的方式完成標記、清理或複製工做
使用這種方式因爲在垃圾回收過程當中,間斷性地還執行了應用程序代碼因此能減小系統的停頓時間。可是由於線程切換和上下文轉換的消耗,會使得垃圾回收的整體成本上升,形成系統吞吐量的降低
================================
分區算法主要仍是針對G1收集器來講的通常來講在相同條件下堆空間越大,一次GC時所須要的時間就越長,有關GC產生的停頓也越長.
爲了更好地控制GC產生的停頓時間,將一塊大的內存區域分割成多個小塊,根據目標的停頓時間,每次合理地回收若干個小區間,而不是整個堆空間,從而減小一次GC所產生的停頓
分代算法將按照對象的生命週期長短劃分紅兩個部分,分區算法將整個堆空間劃分紅連續的不一樣小區間。每個小區間都獨立使用獨立回收。這種算法的好處是能夠控制一次回收多少個小區間
須要注意的是注意,這些只是基本的算法思路實際GC實現過程要複雜的多,目前還在發展中的前沿GC都是複合算法,而且並行和併發兼備