Linux系統Dalvik虛擬機中可以對Android進行一些優化處理,而對堆內存的管理和回收是優化系統的重要手段。本文就來介紹一下Linux系統Dalvik堆內存的管理與回收。
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線程排他地對這些對象進行標記。由於這些對象不是很多所以這個過程很快,這也是分兩步的原因。
以上就是Linux系統Dalvik堆內存的管理與回收的全部內容了,所以Linux系統不僅可以把Dalvik當做一個虛擬機,還能把Dalvik作為有效的優化工具。