浅谈Android垃圾回收机制
Android垃圾回收機制詳解
? 近來在深挖Android的垃圾回收機制,發(fā)現(xiàn)這方面原本數(shù)量少得可憐的技術(shù)文章卻大多早已過時,無奈下只好多方查閱資料,現(xiàn)在我就了解到的情況做一個總結(jié),希望對你有所幫助,如有錯誤歡迎在評論區(qū)指出。
前言
? Android如今使用的虛擬機名叫Android Runtime,簡稱Art(本文后面將用Art來指代Android虛擬機),而Art的其中一大職責(zé)就是負(fù)責(zé)垃圾回收。
? 在講述Art的垃圾回收機制之前,還需要了解Art如何判定一個對象是垃圾。
? 目前主流有兩種判定算法,引用計數(shù)方法和可達(dá)性分析算法,Art采用的是第二種算法,由于引用計數(shù)方法不是本文的重點,下面我僅就可達(dá)性分析算法展開介紹。
? 下面的內(nèi)容截取自《深入理解Java虛擬機的介紹》。
? “當(dāng)前主流的商用程序語言(Java、C#,上溯至前面提到的古老的Lisp)的內(nèi)存管理子系統(tǒng),都是 通過可達(dá)性分析(Reachability Analysis)算法來判定對象是否存活的。這個算法的基本思路就是通過 一系列稱為“GC Roots”的根對象作為起始節(jié)點集,從這些節(jié)點開始,根據(jù)引用關(guān)系向下搜索,搜索過 程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個對象到GC Roots間沒有任何引用鏈相連, 或者用圖論的話來說就是從GC Roots到這個對象不可達(dá)時,則證明此對象是不可能再被使用的。 如下圖所示,對象object 5、object 6、object 7雖然互有關(guān)聯(lián),但是它們到GC Roots是不可達(dá)的, 因此它們將會被判定為可回收的對象。”
圖片來源:《深入理解Java虛擬機》? 至于在Java技術(shù)體系里面,固定可作為GC Roots的對象有哪些的問題由于不是本文的重點這里就不再展開細(xì)講,感興趣的小伙伴可以自行查閱。
? 了解Art如何界定一個對象是垃圾后,我們再來看看它是如何進(jìn)行垃圾清理的。
? 常見的垃圾清理算法有三種,標(biāo)記-清除算法,標(biāo)記-復(fù)制算法,標(biāo)記-整理算法。
? 不同于Dalvik(Android上一代虛擬機)只采用了一種算法的是,Art采用了兩種算法,標(biāo)記-復(fù)制算法,標(biāo)記-整理算法,下面先簡單介紹標(biāo)記-復(fù)制法。
? 下面內(nèi)容截取自《深入理解Java虛擬機的介紹》。
? “標(biāo)記-復(fù)制算法常被簡稱為復(fù)制算法。為了解決標(biāo)記-清除算法面對大量可回收對象時執(zhí)行效率低的問題,1969年Fenichel提出了一種稱為“半?yún)^(qū)復(fù)制”(Semispace Copying)的垃圾收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。如果內(nèi)存中多數(shù)對象都是存活的,這種算法將會產(chǎn)生大量的內(nèi)存間復(fù)制的開銷,但對于多數(shù)對象都是可回收的情況,算法需要復(fù)制的就是占少數(shù)的存活對象,而且每次都是針對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時也就不用考慮有空間碎片的復(fù)雜情況,只要移動堆頂指針,按順序分配即可。這樣實現(xiàn)簡單,運行高效,不過其缺陷也顯而易見,這種復(fù)制回收算法的代價是將可用內(nèi)存縮小為了原來的一半,空間浪費未免太多了一點。標(biāo)記-復(fù)制算法的執(zhí)行過程如下圖所示。 ”
?
圖片來源:《深入理解Java虛擬機》Art中標(biāo)記復(fù)制算法的具體實現(xiàn)
? 在前面引用內(nèi)容中,作者指出如果內(nèi)存中多數(shù)對象都是存活的,標(biāo)記復(fù)制算法將會產(chǎn)生大量的內(nèi)存間復(fù)制的開銷(原文加粗部分),而這正是因為該算法只把內(nèi)存區(qū)域分為了兩個區(qū)域,這就會導(dǎo)致出現(xiàn)復(fù)制絕大部分的存活對象只為了清理掉一小部分垃圾的情況,這種做法無異于在家里打掃衛(wèi)生,為了些許灰塵,把灰塵所在一邊的所有家具才搬到?jīng)]有灰塵的另一邊后才打掃衛(wèi)生,這是一種代價極其高昂的清理垃圾方法。
? 因此,針對這種情況,Art采用的是該算法優(yōu)化過后的版本,把內(nèi)存劃分為多個區(qū)域(官方說法叫做Region),一個區(qū)域大小為256KB,如下圖所示。
? 這種做法顯而易見的好處如下:
? 1.當(dāng)一個區(qū)域沒有垃圾的時候,就可以不進(jìn)行垃圾清理。
? 2.當(dāng)一個區(qū)域因為只有一兩個垃圾而要進(jìn)行垃圾清理的時候,代價也不會太過于高昂,因為一個區(qū)域大小才256KB,本來存儲的對象就不多,因為一兩個垃圾而復(fù)制三四個對象還是可以接受的,這就和在家里打掃衛(wèi)生時因為掃把夠不著椅子底下的灰塵,從而把椅子移開后才進(jìn)行清理一樣可以令人接受。
區(qū)域命名規(guī)則
? 需要注意的是,由于Evacuated這個單詞不太好翻譯,為了避免我個人對這個詞的翻譯影響讀者的理解,后面我在講解Art對區(qū)域的命名規(guī)則的時候仍使用Evacuated這個單詞,讀者可根據(jù)自己的理解對Evacuated進(jìn)行解釋。(ps:Evacuated有疏散;撤離;排泄;騰出(房子等)的意思,詞意來自必應(yīng)詞典)
? 1.當(dāng)一個區(qū)域有垃圾,需要被Evacuated的時候,Art則將該區(qū)域命名為Evacuated Region。
? 2.當(dāng)一個區(qū)域沒有垃圾,不需要被Evacuated的時候,Art則將該區(qū)域命名為Unevacuated Region。
? 3.當(dāng)一個區(qū)域沒有存儲對象的時候,Art則將該區(qū)域命名為Unused Region。
? 4.當(dāng)一個區(qū)域原先為Unused Region,但是要作為其它Evacuated Region中存活對象復(fù)制目的地的時候,Art則將該區(qū)域命名為Evacuation Region。(存活對象即那些沒有被Art判定為垃圾的對象,下同)
? 第一到第三個命名規(guī)則結(jié)合圖片應(yīng)該很好理解,這里就不再贅述,這里我再花點筆墨簡單介紹下Evacuation Region。
? 舉個例子,假設(shè)有兩個區(qū)域,存儲了對象的區(qū)域1和沒有存儲對象的區(qū)域2,Art在使用可達(dá)性分析算法后,發(fā)現(xiàn)區(qū)域1有垃圾,將區(qū)域1命名為Evacuated Region,但區(qū)域1里面還有存活對象,由于區(qū)域2沒有存儲對象,Art決定將這些存活對象要復(fù)制到區(qū)域2,那么此時區(qū)域2就會被Art命名為Evacuation Region。
對象著色規(guī)則
? 細(xì)心的讀者可能會發(fā)現(xiàn),上圖中的對象顏色并不都一樣,深綠色是來標(biāo)明老年代中的存活對象,淺綠色是來標(biāo)明新生代中的存活對象,紅色是來標(biāo)明待清理的垃圾,此外,老年代和新生代都聚集在各自的區(qū)域,并沒有出現(xiàn)老年代和新生代混合在一個區(qū)域的情況,這樣做是有原因的。
? 新生代和老年代都是分代收集理論中的概念,下面再次引用《深入理解Java虛擬機》的內(nèi)容來簡單介紹下分代收集理論。
? “當(dāng)前商業(yè)虛擬機的垃圾收集器,大多數(shù)都遵循了“分代收集”(Generational Collection)的理論進(jìn)行設(shè)計,分代收集名為理論,實質(zhì)是一套符合大多數(shù)程序運行實際情況的經(jīng)驗法則,它建立在兩個分代假說之上:
1)弱分代假說(Weak Generational Hypothesis):絕大多數(shù)對象都是朝生夕滅的。
2)強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消亡。
? 這兩個分代假說共同奠定了多款常用的垃圾收集器的一致的設(shè)計原則:收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū) 域之中存儲。顯而易見,如果一個區(qū)域中大多數(shù)對象都是朝生夕滅,難以熬過垃圾收集過程的話,那么把它們集中放在一起,每次回收時只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對象,就能以較低代價回收到大量的空間;如果剩下的都是難以消亡的對象,那把它們集中放在一塊,虛擬機便可以使用較低的頻率來回收這個區(qū)域,這就同時兼顧了垃圾收集的時間開銷和內(nèi)存的空間有效利用。
? 在Java堆劃分出不同的區(qū)域之后,垃圾收集器才可以每次只回收其中某一個或者某些部分的區(qū)域 ——因而才有了“Minor GC”“Major GC”“Full GC”這樣的回收類型的劃分;也才能夠針對不同的區(qū)域安 排與里面存儲對象存亡特征相匹配的垃圾收集算法——因而發(fā)展出了“標(biāo)記-復(fù)制算法”“標(biāo)記-清除算 法”“標(biāo)記-整理算法”等針對性的垃圾收集算法。這里筆者提前提及了一些新的名詞,它們都是本章的重要角色,稍后都會逐一登場,現(xiàn)在讀者只需要知道,這一切的出現(xiàn)都始于分代收集理論。
…
? 把分代收集理論具體放到現(xiàn)在的商用Java虛擬機里,設(shè)計者一般至少會把Java堆劃分為新生代 (Young Generation)和老年代(Old Generation)兩個區(qū)域。顧名思義,在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。”
? 同樣地,Art也采用了這種分代收集理論,分為Major GC和Full GC(GC為Garbage Collection的簡稱),在Minor GC中只對新生代進(jìn)行可達(dá)性算法分析,在Full GC中才對新生代和老年代一起進(jìn)行可達(dá)性算法分析。
分代收集理論存在的問題
? 把對象單純分為新生代和老年代還存在著一個問題,老年代可能持有新生代的引用,而在Minor GC中Art只對新生代進(jìn)行可達(dá)性算法分析,這樣可能會導(dǎo)致只被老生代持有的新生代被Art誤判為垃圾,舉一個栗子,假設(shè)有一個老年代X持有了新生代Y的引用,且Y的引用只被X所持有,也就是說,只存在由X出發(fā)到Y(jié)的路徑,那么Art在Minor GC由于不對X進(jìn)行可達(dá)性算法分析,會判定Y不可達(dá),從而誤判Y為垃圾,
? 這就是所謂的跨代引用假說,因此,為了解決這問題,Art引入了Remember Set來記錄老年代對新生代的引用。
? 下面我繼續(xù)引用《深入理解Java虛擬機》來對跨代引用假說和Remember Set進(jìn)行介紹。
? “跨代引用假說(Intergenerational Reference Hypothesis):跨代引用相對于同代引用來說僅占極少數(shù)。
? 這其實是可根據(jù)前兩條假說邏輯推理得出的隱含推論:存在互相引用關(guān)系的兩個對象,是應(yīng)該傾向于同時生存或者同時消亡的。舉個例子,如果某個新生代對象存在跨代引用,由于老年代對象難以消亡,該引用會使得新生代對象在收集時同樣得以存活,進(jìn)而在年齡增長之后晉升到老年代中,這時跨代引用也隨即被消除了。
? 依據(jù)這條假說,我們就不應(yīng)再為了少量的跨代引用去掃描整個老年代,也不必浪費空間專門記錄每一個對象是否存在及存在哪些跨代引用,只需在新生代上建立一個全局的數(shù)據(jù)結(jié)構(gòu)(該結(jié)構(gòu)被稱為“記憶集”,Remembered Set),這個結(jié)構(gòu)把老年代劃分成若干小塊,標(biāo)識出老年代的哪一塊內(nèi)存會存在跨代引用。此后當(dāng)發(fā)生Minor GC時,只有包含了跨代引用的小塊內(nèi)存里的對象才會被加入到GC Roots進(jìn)行掃描。雖然這種方法需要在對象改變引用關(guān)系(如將自己或者某個屬性賦值)時維護(hù)記錄數(shù)據(jù)的正確性,會增加一些運行時的開銷,但比起收集時掃描整個老年代來說仍然是劃算的。 ”
Art的Full GC
? 準(zhǔn)確來說,Art采用的并不是Full GC算法,因為根據(jù)谷歌的說法,Art采用的是經(jīng)過優(yōu)化的Full GC算法,全稱叫2-phase full-heap GC cycles,但后文為了介紹方便,仍采用Full GC的說法,稍微有點英文基礎(chǔ)的讀者看到算法的全稱就應(yīng)該知道,該算法分為兩階段,如圖所示,第一階段使用可達(dá)性算法分析來判斷對象是否存活,第二階段就是根據(jù)區(qū)域中的存活對象數(shù)量判斷是否需要進(jìn)行Evacuated。(ps:Full GC未優(yōu)化的版本就包含垃圾判斷和垃圾回收)
圖片來源:谷歌開發(fā)者大會? 如下圖所示,Full GC判斷一個區(qū)域需要Evacuated的標(biāo)準(zhǔn)是該區(qū)域的存活對象數(shù)量小于三個。
? 下圖是Full GC之后的內(nèi)存情況。
Art的垃圾回收周期
? 介紹完Minor GC和Full GC,我們再來看一下Art的垃圾回收周期,如下圖所示,Art一個垃圾回收周期是由一個Full GC的開始到下一個Full GC的開始,但一個周期內(nèi)Minor GC的數(shù)量是不確定的,唯一確定的是兩個Full GC之間的時間間隔。
圖片來源:谷歌開發(fā)者大會? 上圖還是比較好理解的,但有些地方還是需要再解釋下。
? 1.Q是Android的版本號,也就是Android 10,由于Android 10之后谷歌并沒有對Art進(jìn)行大改,所以Android 10之后的版本還是采用了Android 10的垃圾回收算法。
? 2.Young-gen GC cycles直譯過來就是新生代垃圾回收周期,也就是我們上面所說的Minor GC。
Art垃圾回收算法的并發(fā)性
? 注意上面所介紹的垃圾回收算法具有并發(fā)性,也就是說垃圾回收線程是與主線程并發(fā)進(jìn)行的,在一個垃圾回收周期只有一次短暫的GC暫停,時間為幾毫秒,所以用戶大多數(shù)情況下是無法感知的,并不會出現(xiàn)”stop the world“現(xiàn)象。
? 讀取屏障是Art垃圾回收得以實現(xiàn)并發(fā)性的關(guān)鍵,讀取屏障會攔截來自堆的引用讀取。(這一部分安卓官網(wǎng)也沒詳細(xì)介紹,歡迎了解的讀者在下面補充)
Art另一種垃圾回收算法
? 前面說過Art采用了兩種垃圾回收算法。
? 當(dāng)應(yīng)用仍在前臺運行,與用戶進(jìn)行交互的時候,Art采用的就是上面所介紹的算法。
? 而當(dāng)應(yīng)用在后臺運行時,于用戶不可見的時候,Art采用的就是另一種算法,下面簡單引用安卓官網(wǎng)的內(nèi)容進(jìn)行簡單介紹。
? “ART 仍然支持的另一個 GC 方案是 CMS(并發(fā)標(biāo)記清除)。此 GC 方案還支持壓縮,但不是以并發(fā)方式。在應(yīng)用進(jìn)入后臺之前,它會避免執(zhí)行壓縮,應(yīng)用進(jìn)入后臺后,它會暫停應(yīng)用線程以執(zhí)行壓縮。如果對象分配因碎片而失敗,也必須執(zhí)行壓縮操作。在這種情況下,應(yīng)用可能會在一段時間內(nèi)沒有響應(yīng)。”
參考資料
1.《深入理解Java虛擬機》-周志明
2.Understanding Android Runtime (ART) for faster apps (Google I/O’19)
3.安卓官網(wǎng)
總結(jié)
以上是生活随笔為你收集整理的浅谈Android垃圾回收机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gwipr70驱动天空_gwi驱动
- 下一篇: pci-e串口卡linux 驱动下载,P