Android小知識-記憶體回收機制
通過下圖,知道Android系統中最近分配的物件會存放在Young Generation區域。物件在某個時機觸發GC回收垃圾,而沒有回收的就根據不同規則,有可能被移動到Old Generation,最後累計一定時間再移動到PermanentGeneration區域。系統會根據記憶體中不同的記憶體資料型別分別執行不同的GC操作。GC通過確定物件是否被活動物件引用來確定是否收集該物件,進而動態回收無任何引用的物件佔據的記憶體空間。

image
圖中整個記憶體分為三個區域:年輕代、老年代以及持久代。
1、Young Generation
年輕代分為三個區,一個Eden區,另外兩個S0和S1都是Survivor區(S0和S1只是為了說明,兩者實質上一樣)。程式中生成的大部分新的物件都在Eden區,當Eden區滿時,還存活的物件將被複制到其中一個Survivor區,當此Survivor區的物件佔用空間滿時,此區存活的物件又被複制到另一個Survivor區,當這個Survivor區也滿時,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制到老年代。
2、Old Generation
老年代存放的是上面年輕代複製過來的物件,也就是在年輕代中還存活的物件,並且區滿了複製過來的。一般來說,年老代中的物件生命週期都比較長。
3、Permanent Generation
用於存放靜態的類和方法,持久代對垃圾回收沒有顯著的影響。
記憶體物件的處理過程如下:
-
物件建立後在Eden區。
-
執行GC時,如果物件仍然存活,則複製到S0區。
-
當S0區滿時,該區存活物件將複製到S1區,然後S0區清空,接下來SO和S1角色互換。
-
當第3步達到一定次數後,存活物件將被複制到Old Generation。
-
當這個物件在Old Generation區停留的時間達到一定程度時,它會被移動到Old Generation,最後累積一定時間再移動到Permanent Generation區域。Permanent Generation區域存放一些靜態檔案,如Java類等。
系統在Young Generation、Old Generation上採用不同的回收機制。每一個Generation的記憶體區域都有固定的大小。隨著新的物件陸續被分配到此區域,當物件總的大小臨近這一級別記憶體區域的閾值時,會觸發GC操作,以便騰出空間來存放其它新的物件。
執行GC佔用的時間和它發生在哪一個Generation也有關係,Young Generation中的每次GC操作時間最短的,Old Generation其次,Permanent Generation最長。同時GC的執行時間也和當前Generation中的物件數量有關,Generation中的物件數量越多,執行時間越長。
Young Generation通常存活時間較短,因此基於Copying演算法來回收,所謂Copying演算法,就是掃描出存活的物件,並複製到一塊新的完全未使用的空間中,對應於Young Generation,就是在Eden、FromSpace或ToSpace之間copy。新生代採用空閒指標的方式來控制GC觸發,指標保持最後一個分配的物件在Young Generation區間的位置,當有新的物件要分配記憶體時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配物件時,物件會逐漸從Eden到Survivor,最後到Old Generation。
Old Generation與Young Generation不同,物件存活的時間比較長,比較穩定,因此採用標記演算法來回收。所謂標記,就是掃描出存活的物件,然後再回收未被標記的物件,回收後對空出的空間要麼合併,要麼標記出來便於下次分配,以減少記憶體碎片帶來的效率損耗。
在Android系統中,GC有以下三種類型:
-
kGcCauseForAlloc:在分配記憶體時發現記憶體不夠的情況下引起的GC,這種情況下的GC會Stop World。Stop World時由於併發GC時,其他執行緒都會停止,知道GC完成。
-
kGcCauseBackground:當記憶體達到一定的閾值時觸發GC,這個時候時一個後臺GC,不會引起Stop World。
-
kGcCauseExplicit:顯示呼叫時進行的GC,如果ART打開了這個選項,在system.gc時會進行GC。
分析下面這段虛擬機器打印出來的日誌
D/dalvikvm(7030):GC_CONCURRENT freed 1049k, 60% free 2341k/9351k, external 3502k/6261k, paused 3ms 3ms
freed 1049k表面在這次GC中回收了多少記憶體。
60% free 2341k/9351k是Heap的一些統計資料,表面這次回收後60%的Heap可用,存活的物件大小為2341KB,Heap大小是9351 KB。
external 3502k/6261k是Native Memory的資料,存放點陣圖資料或者堆以外記憶體之類的。第一個數字表面Native Memory中已經分類多少記憶體,第二個值有點類似一個浮動的閾值,表面分配記憶體達到這個值,系統就會觸發一次GC進行記憶體回收。
paused 3ms 3ms表面GC暫停的時間。
GC_CONCURRENT是當前GC時的型別,在Android的虛擬機器中GC日誌有以下幾種型別:
-
GC_CONCURRENT:當應用程序中的Heap記憶體佔用上漲時,避免因Heap記憶體滿了而觸發的GC。
-
GC_FOR_MALLOC:這是由於Concurrent GC沒有及時執行完,而應用又需要分配更多的記憶體,這時不得不停下來進行Malloc GC。
-
GC_EXTERNAL_ALLOC:這是為external分配的記憶體執行達到GC。
-
GC_HPROF_DUMP_HEAP:建立一個HPROF profile的時候執行。
-
GC_EXPLICIT:顯式地呼叫了System.gc()。一般來說,可以信任系統的GC機制,儘量不去顯式呼叫System.gc(),減少不必要的系統開銷,影響應用的流暢度。
在ART模式下日誌多了一個Large Object Space(大物件佔用的空間),這部分記憶體並不是分配在堆上的,但仍屬於應用程式記憶體空間,主要用來管理Bitmap等佔記憶體大的物件,避免因分配大記憶體導致頻繁GC。
在Dalvik虛擬機器下,GC的操作都是併發的,也就意味著每次觸發GC都會導致其他執行緒暫停工作(包括UI執行緒)。而在ART模式下,在GC時,不像Dalvik僅有一種回收演算法,ART在不同的情況下會選擇不同的回收演算法,比如Alloc記憶體不夠時會採用非併發GC,但在Alloc後,發現記憶體達到一定閾值時又會觸發併發GC。所以在ART環境下並不是所有的GC都是非併發的。
總體來看,在GC方面,與Dalvik相比,ART更為高效,不僅僅是GC的效率大大縮短了Pause時間,而且在記憶體分配上對大記憶體分配單獨的區域,有演算法在後臺做記憶體整理,減少記憶體碎皮。因此在ART虛擬機器下,可以避免較多的類似GC導致的卡頓問題。
以上內容來自《Android應用效能優化最佳實踐》

掃碼_搜尋聯合傳播樣式-標準色版.png
Android、Java、Python、Go、PHP、IOS、C++、HTML等等技術文章,更有各種書籍推薦和程式設計師資訊,快來加入我們吧!關注技術共享筆記。

838794-506ddad529df4cd4.webp.jpg
搜尋微信“顧林海”公眾號,定期推送優質文章。