2021-05-12 14:32:11
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執行緒排他地對這些物件進行標記。由於這些物件不是很多所以這個過程很快,這也是分兩步的原因。
本文永久更新連結地址:http://www.linuxidc.com/Linux/2015-08/122329.htm
相關文章