Dalvik 堆記憶體管理與回收,dalvik堆記憶體管理
Dalvik虛擬機器用來指派至的堆劃分為兩部分,一部分叫做Active Heap,另一部分叫做Zygote Heap。下面基於管理機制來介紹為何分配為這兩部分,以及堆記憶體的管理。
我們從Android系統啟動說起。
Android系統啟動後,會有一個Zygote進程建立第一個Dalvik虛擬機器,它只維護了一個堆。以後啟動的所有應用程式進程是被Zygote進程fork出來的,並都持有一個自己的Dalvik虛擬機器。在建立應用程式的過程中,Dalvik虛擬機器採用COW策略複製Zygote進程的地址空間。
COW策略:一開始的時候(未複製Zygote進程的地址空間的時候),應用程式進程和Zygote進程共用了同一個用來指派至的堆。當Zygote進程或者應用程式進程對該堆進行寫操作時,核心就會執行真正的拷貝操作,使得Zygote進程和應用程式進程分別擁有自己的一份拷貝,這就是所謂的COW。因為copy是十分耗時的,所以必須盡量避免copy或者盡量少的copy。
為了實現這個目的,當建立第一個應用程式進程時,會將已經使用了的那部分堆記憶體劃分為一部分,還沒有使用的堆記憶體劃分為另外一部分。前者就稱為Zygote堆,後者就稱為Active堆。這樣只需把zygote堆中的內容複寫給應用程式進程就可以了。以後無論是Zygote進程,還是應用程式進程,當它們需要指派至的時候,都在Active堆上進行。這樣就可以使得Zygote堆儘可能少地被執行寫操作,因而就可以減少執行寫時拷貝的操作。在Zygote堆裡面分配的對象其實主要就是Zygote進程在啟動過程中預先載入的類、資源和對象了。這意味著這些預先載入的類、資源和對象可以在Zygote進程和應用程式進程中做到長期共用。這樣既能減少拷貝操作,還能減少對記憶體的需求。
類似於JVM,Dalvik虛擬機器也需要負責對堆記憶體中的對象進行管理工作,它使用的也是標記清除演算法,但是細節上略有區別。
Mark-Sweep演算法分為兩個階段:
- Mark階段:通過遞迴對象的引用,從對象的根集開始標記被引用的對象。
- Sweep階段:回收沒有被標記的對象佔用的記憶體。
Dalvik虛擬機器通過Heap Bitmap來標記標記對象有沒有被引用。所謂Heap Bitmap就是一個unsigned long數組,如果一個對象被引用,那麼在Bitmap中與它對應的那一位就會被設定為1。否則的話,就設定為0。Dalvik使用了兩個Bitmap來描述堆的對象,一個稱為Live Bitmap,另一個稱為Mark Bitmap。Live Bitmap用來標記上一次GC時被引用的對象,也就是沒有被回收的對象,而Mark Bitmap用來標記當前GC有被引用的對象。這樣只需要回收上一次被引用,當前未被引用的對象就可以了。
在垃圾收集的Mark階段,要求除了垃圾收集線程之外,其它的線程都停止(Stop The World),否則如果對象在GC過程中又引用了其他對象,就會可能導致不能正確地標記每一個對象。然而,這將造成程式卡頓,效率降低。所以必須允許在Mark階段使記憶體回收線程和其他線程可以並發執行(Concurrent GC)。
為了實現此目的,Dalvik將Mark階段劃分為兩步:
- 第一步,只標記根集對象,即在GC過程開始的時刻,那些被全域變數,棧變數,寄存器對象引用的對象。這個階段只允許GC線程運行,防止這些根集對象在這個過程中再去引用其他對象。
- 第二步,通過這些根集對象參考關聯性,可以找到並標記其他正在使用的對象。這個階段可以允許其他線程與GC線程並發執行。為了實現GC線程與其他線程並發,需要把其他線程對對象的修改記錄下來,記錄這些修改的資料結構被稱為Card Table。
Dalvik虛擬機器進行部分垃圾收集時,實際上就是只收集在Active堆上分配的對象。因此對Dalvik虛擬機器來說,Card Table就是用來記錄在Zygote堆上分配的對象在部收垃圾收集執行過程中對在Active堆上分配的對象的引用。
與Bitmap不同,Card Table中每個card大小為一個位元組,如果與它對應的對象在第二步未被修改過,其值為clean,否則為dirty。對於被修改過的對象,在第二步結束後需要重新使用GC線程排他地對這些對象進行標記。由於這些對象不是很多所以這個過程很快,這也是分兩步的原因。