標記階段
對象存活判斷
- 在堆里存放著幾乎所有的Java對象實例,在GC執(zhí)行垃圾回收之前缔杉,首先需要區(qū)分出內存中哪些是存活對象,哪些是已經死亡的對象搁料。只有被標記為己經死亡的對象或详,GC才會在執(zhí)行垃圾回收時,釋放掉其所占用的內存空間郭计,因此這個過程我們可以稱為垃圾標記階段霸琴。
- 那么在JVM中究竟是如何標記一個死亡對象呢?簡單來說昭伸,當一個對象已經不再被任何的存活對象繼續(xù)引用時梧乘,就可以宣判為已經死亡。
- 判斷對象存活一般有兩種方式:引用計數(shù)算法和可達性分析算法庐杨。
- 垃圾回收這個動作通過推理可知选调,至少需要兩個步驟:1.判斷對象是否是垃圾 2.回收垃圾 ;這里標記階段就是第一步灵份,判斷對象是否是垃圾仁堪。
notice:
那些地方有GC
meta space 和 heap邏輯上是都有GC和OOM的,pc計數(shù)器沒有GC,OOM,stack沒有GC填渠,有SOF弦聂。
方法區(qū)確實有GC,但是不是虛擬機必須清理的,所以這里說的GC主要說的是heap
標記階段的兩種算法
引用計數(shù)法 (java沒有采用)
- 引用計數(shù)算法(Reference Counting)比較簡單氛什,對每個對象保存一個整型 的引用計數(shù)器屬性莺葫。用于記錄對象被引用的情況。* 對于一個對象A枪眉,只要有任何一個對象引用了A徙融,則A的引用計數(shù)器就加1;當引用失效時瑰谜,引用計數(shù)器就減1欺冀。只要對象A的引用計數(shù)器的值為0,即表示對象A不可能再被使用萨脑,可進行回收隐轩。
優(yōu)點:
實現(xiàn)簡單,垃圾對象便于辨識渤早;判定效率高职车,回收沒有延遲性。
缺點:
- 它需要單獨的字段存儲計數(shù)器,這樣的做法增加了存儲空間的開銷悴灵。
- 每次賦值都需要更新計數(shù)器扛芽,伴隨著加法和減法操作,這增加了時間開銷积瞒。
- 引用計數(shù)器有一個嚴重的問題川尖,即無法處理循環(huán)引用的情況。這是一 條致命缺陷茫孔,導致==在Java的垃圾回收器中沒有使用這類算法
notice
n1.缺點1,2是可以推理到的
其實這種引用計數(shù)法的實現(xiàn)思路是很容易讓大家想到的叮喳,即所有對象都維護一個變量存儲當前對象被引用的次數(shù),如果為0就標記為垃圾缰贝,但是這種方式必然兩方面的確定:
1.開銷內存空間
2.每次變量的重新賦值馍悟,對象應用的變化,都會導致剩晴,對象引用計數(shù)器的動態(tài)更新锣咒。
以上兩點是可以直接推理出的缺點。
n2.循環(huán)引用
循環(huán)引用指的是一個變量引用一個對象A赞弥,對象A的引用計數(shù)器屬性為1毅整,同時這個對象里面的一些變量,指向對象B嗤攻,而對象B中有變量指向對象C毛嫉,對象C中有變量指向對象A,這樣就造成了一個循環(huán)引用妇菱,而此時對象A的引用計數(shù)器屬性在原有的基礎上+1承粤,變成2。
一旦這個變量P不再指向對象A闯团,那么此時其引用計數(shù)器屬性變成了1辛臊,但是因為對象循環(huán)引用其引用計數(shù)器屬性不會變成0,那么其也就不會被標記為垃圾房交,造成內存泄露彻舰。
可達性分析算法
可達性分析算法,即將活躍的引用都放到一組集合中候味,每個存放到集合中的引用都作為根節(jié)點即GC Roots,每個被引用的對象都通過一條線和GC Roots連接刃唤,那么只有在內存中被GC Roots連接的對象才是存活的對象。
比如:此時有一個對象object1,其連接GC Roots,而object1中也有對象變量object2,那么object2直接連接object1白群,就算發(fā)生循環(huán)引用尚胞,那么只需要判斷object1是否和GC Roots連接即可判斷其是否為垃圾。
基本思路:
- 可達性分析算法是以根對象集合(GCRoots)為起始點帜慢,按照從上至下的方式搜索被根對象集合所連接的目標對象是否可達笼裳。
- 使用可達性分析算法后唯卖,內存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈(Reference Chain)
- 如果目標對象沒有任何引用鏈相連躬柬,則是不可達的拜轨,就意味著該對象己經死亡,可以標記為垃圾對象允青。
- 在可達性分析算法中橄碾,只有能夠被根對象集合直接或者間接連接的對象才是存活對象。
GC Roots應該包含的元素
- 虛擬機棧中引用的對象
比如:各個線程被調用的方法中使用到的參數(shù)昧廷、局部變量等堪嫂。 - 本地方法棧內JNI(通常說的本地方法)引用的對象
方法區(qū)中類靜態(tài)屬性引用的對象(1.7后存儲在heap中)
比如:Java類的引用類型靜態(tài)變量 - 方法區(qū)中常量引用的對象
比如:字符串常量池(string Table) 里的引用 - 所有被同步鎖synchroni zed持有的對象
- Java虛擬機內部的引用偎箫。
基本數(shù)據(jù)類型對應的Class對象木柬,一些常駐的異常對象(如:NullPointerException、OutOfMemoryError) 淹办,系統(tǒng)類加載器眉枕。 - 反映java虛擬機內部情況的JMXBean、JVMTI中注冊的回調怜森、本地代碼緩存等
- 除了這些固定的GCRoots集合以外速挑,根據(jù)用戶所選用的垃圾收集器以及當前回收的內存區(qū)域不同,還可以有其他對象“臨時性”地加入副硅,共同構成完整GC Roots集合姥宝。
比如:分代收集和局部回收(Partial GC)。
如果只針對Java堆中的某一塊區(qū)域進行垃圾回收(比如:典型的只針 對新生代)恐疲,必須考慮到內存區(qū)域是虛擬機自己的實現(xiàn)細節(jié)腊满,更不是孤立封閉的,這個區(qū)域的對象完全有可能被其他區(qū)域的對象所引用培己,這時候就需要一.并將關聯(lián)的區(qū)域對象也加入GC Roots集合中去考慮碳蛋,才能保證可達性分析的準確性。比如:只回收new space 時需要將 old space中的指向也考慮進去省咨。
上面列了那么多肃弟,其實就是想說,因為Root采用棧方式存放變量和指針零蓉,所以如果一個指針笤受,它保存了堆內存里面的對象,但是自己又不存放在堆內存里面敌蜂,那它就是一個Root箩兽。即將堆外,所有對于堆的指向都考慮到GC Roots中紊册。
notice:
如果要使用可達性分析算法來判斷內存是否可回收比肄,那么分析工作必須在 一個能保障一致性的快照中進行快耿。這點不滿足的話分析結果的準確性就無法保證。如同事物的一致性一樣芳绩,即一個業(yè)務中操作掀亥,就是此時不能有其他業(yè)務操作改變GC Root 的連接。也就是保證每個GC Root的對象引用業(yè)務的一致性妥色。(STW)
這點也是導致GC進行時必須“StopTheWorld"的一個重要原因搪花。
?即使是號稱(幾乎)不會發(fā)生停頓的CMS收集器中,枚舉根節(jié)點時也是必須要停頓的嘹害。
對象的finalization機制
Java語言提供了對象終止(finalization)機制來允許開發(fā)人員提供對象被銷毀之前的自定義處理邏輯撮竿。
當垃圾回收器發(fā)現(xiàn)沒有引用指向一個對象,即:垃圾回收此對象之前笔呀,總會先調用這個對象的finalize()方法幢踏。
finalize()方法允許在子類中被重寫,用于在對象被回收時進行資源釋放许师。通常在這個方法中進行一些資源釋放和清理的工作房蝉,比如關閉文件、套接字和數(shù)據(jù)庫連接等微渠。
應該交給垃圾回收機制調用搭幻。理由包括下面三點:永遠不要主動調用某個對象的finalize ()方法
在finalize() 時可能會導致對象復活。
finalize()方法的執(zhí)行時間是沒有保障的逞盆,它完全由Gc線程決定檀蹋,極端情況下,若不發(fā)生GC云芦,則finalize() 方法將沒有執(zhí)行機會俯逾。
一個糟糕的finalize ()會嚴重影響GC的性能。
從功能上來說焕数,finalize()方法與C++ 中的析構函數(shù)比較相似纱昧,但是Java采用的是基于垃圾回收器的自動內存管理機制,所以finalize()方法在本質堡赔,上不同于C++ 中的析構函數(shù)识脆。
對象是否"死亡"
由于finalize ()方法的存在,虛擬機中的對象一般處于三種可能的狀態(tài)善已。
如果從所有的根節(jié)點都無法訪問到某個對象灼捂,說明對象己經不再使用了。一般來說换团,此對象需要被回收悉稠。但事實上,也并非是“非死不可”的艘包,這時候它們暫時處于“緩刑”階段的猛。一個無法觸及的對象有可能在某一個條件下“復活”自己耀盗,如果這樣,那么對它的回收就是不合理的卦尊,為此叛拷,定義虛擬機中的對象可能的三種狀態(tài)。如下:
可觸及的:從根節(jié)點開始岂却,可以到達這個對象忿薇。
可復活的:對象的所有引用都被釋放,但是對象有可能在finalize()中復活躏哩。
不可觸及的:對象的finalize()被調用署浩,并且沒有復活,那么就會進入不可觸及狀態(tài)扫尺。不可觸及的對象不可能被復活筋栋,因為finalize() 只會被調用一一次。
以上3種狀態(tài)中器联,是由于finalize()方法的存在二汛,進行的區(qū)分婿崭。只有在對象不可觸及時才可以被回收拨拓。
判定是否可以回收具體過程
判定一個對象objA是否可回收,至少要經歷兩次標記過程:
如果對象objA到GC Roots沒有引用鏈氓栈,則進行第一 次標記渣磷。
進行篩選,判斷此對象是否有必要執(zhí)行finalize()方法
如果對 象objA沒有重寫finalize()方法授瘦,或者finalize ()方法已經被虛擬機調用過醋界,則虛擬機視為“沒有必要執(zhí)行”,objA被判定為不可觸及的提完。
如果對象objA重寫了finalize()方法形纺,且還未執(zhí)行過,那么objA會被插入到F一Queue隊列中徒欣,由一個虛擬機自動創(chuàng)建的逐样、低優(yōu)先級的Finalizer線程觸發(fā)其finalize()方法執(zhí)行。
finalize()方法是對象逃脫死亡的最后機會打肝,稍后Gc會對F一Queue隊列中的對象進行第二次標記脂新。如果objA在finalize()方法中與引用鏈上的任何一個對象建立了聯(lián)系,那么在第二次標記時粗梭,objA會被移出“即將回收”集合争便。之后,對象會再次出現(xiàn)沒有引用存在的情況断医,在這個情況下滞乙,finalize方法不會被再次調用奏纪,對象會直接變成不可觸及的狀態(tài),也就是說斩启,一個對象的finalize方法只會被調用一次亥贸。
code
重寫finalization方法
/**
* 測試Object類中finalize()方法,即對象的finalization機制浇垦。
*
*/
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建立了聯(lián)系
}
public static void main(String[] args) {
try {
obj = new CanReliveObj();
// 對象第一次成功拯救自己
obj = null;
System.gc();//調用垃圾回收器
System.out.println("第1次 gc");
// 因為Finalizer線程優(yōu)先級很低,暫停2秒男韧,以等待它
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();
// 因為Finalizer線程優(yōu)先級很低,暫停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
調用當前類重寫的finalize()方法
obj is still alive
第2次 gc
obj is dead
消除階段
當成功區(qū)分出內存中存活對象和死亡對象后甚纲,GC接下來的任務就是執(zhí)行垃圾回收,釋放掉無用對象所占用的內存空間朦前,以便有足夠的可用內存空間為新對象分配內存.
目前在JVM中比較常見的三種垃圾收集算法:
- 標記一清除算法( Mark一Sweep)
- 復制算法(Copying)
- 標記一壓縮算法(Mark一Compact)
標記一清除算法(Mark一Sweep)
執(zhí)行邏輯
當堆中的有效內存空間(available memory) 被耗盡的時候介杆,就會停止整個程序(也被稱為stop the world),然后進行兩項工作韭寸,第一項則是標記春哨,第二項則是清除。
- 標記: Collector從引用根節(jié)點開始遍歷恩伺,標記所有被引用的對象赴背。一般是在對象的Header中記錄為可達對象。
- 清除: Collector對堆 內存從頭到尾進行線性的遍歷晶渠,如果發(fā)現(xiàn)某個對象在其Header中沒有標記為可達對象凰荚,則將其回收。
缺點
- 效率不算高
- 在進行Gc的時候褒脯,需要停止整個應用程序便瑟,導致用戶體驗差
- 這種方式清理出來的空閑內存是不連續(xù)的,產生內存碎片番川。需要維護一個空閑列表
notice:
空閑列表
這個就是為對象分配內存時的一種方式到涂,如果內存規(guī)整:指針碰撞,如果內存不規(guī)整:虛擬機維護一個列表爽彤,記錄那些空間被占用养盗,那些空間沒被占用。
清除
這里說的清楚不是真的將對象清除适篙,而是將對象的地址保存在空閑列表中往核,下次有新對象需要加載時,如果垃圾空間能夠存放嚷节,那么直接覆蓋垃圾空間聂儒。
復制算法
為了解決標記一清除算法在垃圾收集效率方面的缺陷(需要兩步才能清楚:標記虎锚,清除),M.L.Minsky于1963年發(fā)表了著名的論文衩婚,“ 使用雙存儲區(qū)的Li sp語言垃圾收集器CALISP Garbage Collector Algorithm Using SerialSecondary Storage )”窜护。M.L. Minsky在該論文中描述的算法被人們稱為復制(Copying)算法,它也被M. L.Minsky本人成功地引入到了Lisp語言的一個實現(xiàn)版本中非春。
核心思想
將活著的內存空間分為兩塊柱徙,每次只使用其中一塊,在垃圾回收時將正在.使用的內存中的存活對象復制到未被使用的內存塊中奇昙,之后清除正在使用的內存塊中的所有對象护侮,交換兩個內存的角色,最后完成垃圾回收储耐。
堆中S0和S1使用的就是復制算法
優(yōu)點:
- 沒有標記和清除過程羊初,實現(xiàn)簡單,運行高效
- 復制過去以后保證空間的連續(xù)性什湘,不會出現(xiàn)“碎片”問題长赞。
缺點:
此算法的缺點也是很明顯的,就是需要兩倍的內存空間闽撤,且同時有一個內存空間是浪費的得哆。
對于G1(現(xiàn)在jdk默認的垃圾回收器)這種分拆成為大量region(分區(qū))的GC,復制而不移動腹尖,意味著GC需要維護region之間對象引用關系柳恐,不管是內存占用或者時間開銷也不小。
復制算法热幔,復制的過程,伴隨著引用指針的變化讼庇,也就是對象地址發(fā)生變化绎巨,指向其的指針也需要變化,對于G1這種分區(qū)GC的算法蠕啄,如果采用復制算法场勤,那么GC中必然要維護分區(qū)和對象引用的關系蘑险,且對象引用還總變劫瞳,那么GC必須頻繁維護,時間開銷也是很大的狠鸳。
其實消除階段的這幾種算法哈街,主要就是維護對象引用的方式不同留瞳。
使用前提:
如果系統(tǒng)中的垃圾對象很多,那么復制算法處理起來就很麻煩了,復制算法還是需要在復制的存活對象數(shù)量并不會太大骚秦,或者說非常低才行情況下才使用她倘。
應用場景:
在新生代中璧微,對象往往朝生夕死,死亡率很高硬梁,存活的對象相對很少前硫,所以是適用這種復制算法。
在新生代荧止,對常規(guī)應用的垃圾回收屹电,一次通常可以回收708一 99的內存空間跃巡∴拖辏回收性價比很高。所以現(xiàn)在的商業(yè)虛擬機都是用這種收集算法回收新生代瓷炮。
標記-壓縮(整理,Mark-Compact)算法
背景:
復制算法的高效性是建立在存活對象少葱色、垃圾對象多的前提下的。這種情況在新生代經常發(fā)生娘香,但是在老年代苍狰,更常見的情況是大部分對象都是存活對象。如果依然使用復制算法烘绽,由于存活對象較多淋昭,復制的成本也將很高。因此安接,基于老年代垃圾回收的特性翔忽,需要使用其他的算法。
??標記一清除算法的確可以應用在老年代中盏檐,但是該算法不僅執(zhí)行效率低下歇式,而且在執(zhí)行完內存回收后還會產生內存碎片,所以JVM的設計者需要在此基礎之上進行改進胡野。標記一壓縮(Mark一Compact) 算法由此誕生材失。
??1970年前后,G. L. Steele 硫豆、C. J. Chene和D.S. Wise 等研究者發(fā)布標記一壓縮算法龙巨。在許多現(xiàn)代的垃圾收集器中,人們都使用了標記一壓縮算法或其改進版本熊响。
執(zhí)行過程:
- 第一階段和標記一清除算法一樣旨别,從根節(jié)點開始標記所有被引用對象.
- 第二階段將所有的存活對象壓縮到內存的一端,按順序排放汗茄。
-
之后秸弛,清理邊界外所有的空間。
image.png - 標記一壓縮算法的最終效果等同于標記一清除算法執(zhí)行完成后,再進行一次內存碎片整理胆屿,因此奥喻,也可以把它稱為標記一清除一壓縮(Mark一 Sweep一Compact)算法。
- 二者的本質差異在于標記一清除算法是一種非移動式的回收算法非迹,標記一壓.縮是移動式的环鲤。是否移動回收后的存活對象是一項優(yōu)缺點并存的風險決策。
- 可以看到憎兽,標記的存活對象將會被整理冷离,按照內存地址依次排列,而未被標記的內存會被清理掉纯命。如此一來西剥,當我們需要給新對象分配內存時,JVM只需要持有一個內存的起始地址即可亿汞,這比維護一個空閑列表顯然少了許多開銷瞭空。
優(yōu)點
- 消除了標記一清除算法當中,內存區(qū)域分散的缺點疗我,我們需要給新對象分配內存時咆畏,JVM只 需要持有一個內存的起始地址即可。
- 消除了復制算法當中吴裤,內存減半的高額代價旧找。
缺點
- 從效率.上來說,標記一整理算法要低于復制算法麦牺。
- 移動對象的同時钮蛛,如果對象被其他對象引用,則還需要調整引用的地址剖膳。移動過程中魏颓,需要全程暫停用戶應用程序。即: STW(StopTheWorld)
notice:
指針碰撞(Bump the Pointer )
如果內存空間以規(guī)整和有序的方式分布潮秘,即已用和未用的內存都各自一邊琼开,彼此之間維系著一個記錄下一次分配起始點的標記指針,當為新對象分配內存時枕荞,只需要通過修改指針的偏移量將新對象分配在第一個空閑內存位置上,這種分配方式就叫做指針碰撞(Bump the Pointer) 搞动。
指針碰撞適合:標記壓縮躏精,復制算法
空閑列表:適合標記清除算法。
三者算法比較
- 效率上來說鹦肿,復制算法是當之無愧的老大矗烛,但是卻浪費了太多內存。
- 而為了盡量兼顧上面提到的三個指標,標記一整理算法相對來說更平滑一些瞭吃,但是效率.上不盡如人意碌嘀,它比復制算法多了一個標記的階段,比標記一清除多了一個整理內存的階段歪架。
Mark-Sweep | Mark-Compact | Copying | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空間開銷 | 少(但會堆積碎片) | 少(不堆積碎片) | 通常需要活對象的2倍大小(不堆積碎片) |
移動對象 | 否 | 是 | 是 |
分代收集算法
難道就沒有一種最優(yōu)的算法么?
沒有最好的算法,只有某個階段更合適的算法
前面所有這些算法中股冗,并沒有一種算法可以完全替代其他算法,它們都具有自己獨特的優(yōu)勢和特點和蚪。分代收集算法應運而生止状。
分代收集算法,是基于這樣一個事實:不同的對象的<u style="color:rgb(255, 0, 0)">生命周期</u>是不一樣的攒霹。因此怯疤,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率催束。一般是把Java堆分為新生代和老年代集峦,這樣就可以根據(jù)各個年代的特點使用不同的回收算法,以提高垃圾回收的效率抠刺。
在Java程序運行的過程中塔淤,會產生大量的對象,其中有些對象是與業(yè)務信息相關矫付,比如Http請求中的Session對象凯沪、線程、Socket連接买优, 這類對象跟業(yè)務直接掛鉤妨马,因此生命周期比較長。但是還有一些對象杀赢,主要是程序運行過程中生成的臨時變量烘跺,這些對象生命周期會比較短,比如: String對象脂崔, 由于其不變類的特性滤淳,系統(tǒng)會產生大量的這些對象,有些對象甚至只用一次即可回收砌左。
目前幾乎所有的GC都是采用分代收集(Generational Collecting) 算法執(zhí)行垃圾回收的脖咐。 ??在HotSpot中,基于分代的概念汇歹,GC所使用的內存回收算法必須結合年輕代和老年代各自的特點屁擅。
-
年輕代(Young Gen)
- 年輕代特點:區(qū)域相對老年代較小,對象生命周期短产弹、存活率低派歌,回收頻繁。
- 這種情況采用復制算法的回收整理,速度是最快的胶果。復制算法的效率只和當前存活對象大小有關匾嘱,因此很適用于年輕代的回收。而復制算法內存利用率不高的問題早抠,通過hotspot中的兩個survivor的設計得到緩解霎烙。·
-
老年代(Tenured Gen)
- 老年代特點:區(qū)域較大贝或,對象生命周期長吼过、存活率高,回收不及年輕代頻繁咪奖。
- 這種情況存在大量存活率高的對象盗忱,復制算法明顯變得不合適。一般是由標記一清除或者是標記一清除與標記一整理的混合實現(xiàn)羊赵。
- ?Mark階段的開銷與存活對象的數(shù)量成正比趟佃。
- ?Sweep階段的開銷與所管理區(qū)域的大小成正相關。
- ?Compact階 段的開銷與存活對象的數(shù)據(jù)成正比昧捷。
以HotSpot中的CMS回收器為例闲昭,CMS是基于Mark一 Sweep實現(xiàn)的,對于對象的回收效率很高靡挥。而對于碎片問題序矩,CMS采用基于Mark一Compact算法的Serial 0ld回收器作為補償措施:當內存回收不佳(碎片導致的Concurrent Mode Failure時),將采用Serial 0ld執(zhí)行Full GC以達到對老年代內存的整理跋破。
??分代的思想被現(xiàn)有的虛擬機廣泛使用簸淀。幾乎所有的垃圾回收器都區(qū)分新生代和老年代。
增量收集算法毒返、分區(qū)算法
增量收集算法
上述現(xiàn)有的算法租幕,在垃圾回收過程中,應用軟件將處于一種stop the World的狀態(tài)拧簸。在Stop the World狀態(tài)下劲绪,應用程序所有的線程都會掛起,暫停一切正常的工作盆赤,等待垃圾回收的完成贾富。如果垃圾回收時間過長,應用程序會被掛起很久牺六,將嚴重影響用戶體驗或者系統(tǒng)的穩(wěn)定性祷安。為了解決這個問題,即對實時垃圾收集算法的研究直接導致了增量收集(Incremental Collecting) 算法的誕生兔乞。
基本思想
如果一次性將所有的垃圾進行處理,需要造成系統(tǒng)長時間的停頓,那么就可以讓垃圾收集線程和應用程序線程交替執(zhí)行庸追。每次霍骄,垃圾收集線程只收集一小片區(qū)域的內存空間,接著切換到應用程序線程淡溯。依次反復读整,直到垃圾收集完成。
??總的來說咱娶,增量收集算法的基礎仍是傳統(tǒng)的標記一清除和復制算法米间。增量收集算法通過對線程間沖突的妥善處理,允許垃圾收集線程以分階段的方式完成標記膘侮、清理或復制工作屈糊。
缺點:
使用這種方式,由于在垃圾回收過程中琼了,間斷性地還執(zhí)行了應用程序代碼逻锐,所以能減少系統(tǒng)的停頓時間。但是雕薪,因為線程切換和上下文轉換的消耗昧诱,會使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降所袁。
分區(qū)算法
一般來說盏档,在相同條件下,堆空間越大燥爷,一次GC時所需要的時間就越長蜈亩,有關GC產生的停頓也越長。為了更好地控制GC產生的停頓時間局劲,將一塊 大的內存區(qū)域分割成多個小塊勺拣,根據(jù)目標的停頓時間,每次合理地回收若干個小區(qū)間鱼填,而不是整個堆空間药有,從而減少一次GC所產生的停頓。
??分代算法將按照對象的生命周期長短劃分成兩個部分苹丸,分區(qū)算法將整個堆空間劃分成連續(xù)的不同小區(qū)間愤惰。
??每一個小區(qū)間都獨立使用,獨立回收赘理。這種算法的好處是可以控制一次回收多少個小區(qū)間宦言。