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