Dalvik虛擬機詳解(上)

什麼是Dalvik?

    Android平臺是爲那些處理能力、內存、和存儲等處理能力受限的設備而生。
    Android應用程序在運行時必須支持多種不同類型的設備,並且基於安全、性能和可靠性考慮必須被沙箱隔離。如此看來,,虛擬機貌似是非常合適的選擇。
    但是虛擬機在運行時是沒法幫你保證種類繁多的設備的處理速度、和ARM的差異等等,那麼Google是如何處理這些衝突的呢?總的來說,他們的方法就是通過給予運行的程序如下的限制來實現:
    每一個Android應用和它的Dalvik實例都運行在它自己的進程中, Dalvik的設計初衷就是在同一個設備上支持大量的Dalvik實例高效運行的。Dalvik虛擬機在其內部的執行器中執行的是dex格式的文件。Dalvik虛擬機是基於寄存器的,並且執行的是通過dxtool工具將class文件轉換後的dex文件。Dalvik虛擬機的一些因此的功能是依賴於Linux內核的,比如線程和底層的內存管理。
    要想Android應用在它自己的虛擬機進程內執行,不僅僅要求大量的虛擬機高效的運行而且還要求新虛擬機能夠快速創建(用戶會不定時打開新的應用)。

  class文件格式和dex文件格式如下圖所示:

 
     dex文件使用了共享格式清晰的常量池機制。一個常量池存儲了所有的在class文件中使用到的字面意義上的常量值,這些值包括諸如字符串常量、變量、類、接口和方法名稱等。與class文件直接存儲這些值不同,他們總是通過他們在常量池的下標來引用的。在class文件中,每一個類都有它自己私有的、種類繁多的常量池。
    這些種類繁多的常量(字段、變量、class 、etc)是混合着用的。和dex文件形成鮮明的對比的是,dex文件包含很多類,他們都公用一個類型明確的常量池。在class文件中的常量的副本在dex文件中被消除了。
    也許你會問難道共享一個單一的不同的線程池還不夠嗎,爲什麼要去使用類型明確的常量池?實際上類型明確的常量池最大程度上消除了重複的值.這樣做有利於節省大筆內存。
   但是有必要強調的是,內存共享不是免費的,垃圾回收策略必須考慮到共享的內存,因爲GC是每個應用所獨立特有的,儘管他們共享了一些內存,但是每一個app都是一個獨立的進程、獨立的Dalvik虛擬機、獨立的堆內存。目前的策略就是在Dalvik的GC中做標記位,或者標記爲表明一個特殊的對象是可達的,因此不應該被回收、從其它的堆中分離出來。如果標記爲是對象的堆上存儲,當GC遍歷堆設置標記的期間所有共享的對象將會直接“unshared”,記住,共享內存只能是隻讀的,不能改寫。從其它的堆內存分離標記位可避免此陷阱。

什麼是Zygote?

    Zygote的設計使用了一定數量的核心庫類和符合標準的堆結構由應用程序使用,這種堆結構是隻讀的不能被改寫。換句話說就是:這些數據和類是被大多數應用使用的,但是絕不能被修改。這些特性是用於跨進程內存共享。
    Zygote是一個虛擬機進程,當系統開機時就啓動的,當一個Zygote進程啓動時,他會初始化一個Dalvik虛擬機,這個徐幫你記會與先加載和初始化核心庫類。通常這些核心庫類是並且只讀的,因此這是很有益處的對於跨進程共享和預加載來說。
一旦Zygote被初始化,它就會等待一個來自運行時的進程發送來的Socket請求,此請求用於告知Zygote是否需要基於Zygote虛擬機實例去fork一個新的VM實例。
冷啓動一個虛擬機會花費比較長的時間它會將每個應用分離到他們自己的虛擬機內。通過Zygote孵化新的虛擬機進程就大大減少了系統啓動時間了。
    核心庫類在應用虛擬機實例之間是共享使用的,但是隻能讀取卻不能被修改。當這些類被寫入的時候,共享的Zygote進程的內存被複制去fork應用虛擬機的子進程同時寫進去。
    在傳統的java 虛擬機中,每個虛擬機的實例都將有一個核心庫類文件和與之相關的堆對象的複本實例,虛擬機實例之間的內存是不共享的。

虛擬機如何啓動?

    當系統啓動時Zygote進程就會啓動,當Zygote啓動完成後,就會去監聽一個socket上接收的命令。其它的進程比如ActivityManagerServie需要創建一個新的進程時就會把命令寫入到那個Socket。而這個命令是由Zygote進程準備並且調用fork()方法產生的,所以子進程就獲得一個虛擬機去運行了。

    具體來說就是:

    當Android系統的內核被加載後,init.rc會被解析同時本地服務啓動,也就是system/bin/app_process開始運行(frameworks/base/cmds/app_process/app_main.cpp) )。這個進程最終會調用AndroidRuntime.start()方法(frameworks/base/core/jni/AndroidRuntime.cpp)),傳遞給他一個com.android.internal.os.ZygoteInt和start-system-server參數。AndroidRuntime.start()會啓動Java虛擬機,然後調用ZygoteInit.main()方法,並且傳遞一個start-system-server參數。
     然後ZygoteInit.main()首先會去註冊一個Zygote Socket(這個Zygote Socket用於監聽接收到的指令,並且按照要求產生新的進程)。下一步就是預加載很多類(也就是在frameworks/base/preloaded-classes中的列表)以及整個系統的資源比如drawable、xml等。然後就會調用startSystemServer()開始爲SystemServer孵化一個新的虛擬機進程(順便說一句,在SystemServer中的main()方法就是主入口了)。與其他進程相比孵化SystemServer比較特殊,。
     在SystemServer孵化以後,runSelectLoopMode()方法被調用。這是一個while(true)死循環,是用於基本的ZygoteConnect的建立,主要是用於Zygote Socket等待新的命令。當收到新的命令時,ZygoteConnection.runOnce()方法被調用(frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java)。ZygoteConnection.runOnce()調用Zygote.forkAndSpecialize()就是簡單的調用本地方法去fork。可以看出來,SystemServer進程和普通的進程是有區別的,SystemServer是完全由本地類app_main.cpp和AndroidRuntime.cpp來啓動的,而普通的進程可以說是由SystemServer.java配合本地方法完成的。

   

                                    dalvik啓動過程(圖1)                                                                                 dalvik啓動過程(圖2)

什麼是dalvik-cache?

     通常在Android平臺設備上,都會有一個data/dalvik-cache目錄,該目錄存放的是優化過的dex文件。通常第一次安裝app時,系統會啓動一個優化進程optimization  process來對dex文件進行優化,此優化文件被稱爲odex文件,此文件就存放在data/dalvik-cache目錄下,以後app每次啓動就不用再開啓優化進程去優化文件了,這也是爲什麼第一次啓動app時會比較緩慢,而之後啓動會很快的原因。

如何理解Dalvik是基於寄存器的?

  詳見我的另一篇博客:http://blog.csdn.net/u012481172/article/details/50904574;  

  友情鏈接:http://opensourceforu.efytimes.com/2011/06/virtual-machines-for-abstraction-dalvik-vm/

Dalvik的線程狀態

  • INITIALIZING(初始化):還沒開始運行。
  • STARTING(開始): 還沒運行,但是一直存在。
  • ZOMBIE(殭屍?):死亡,我們看不到這種狀態。
  • RUNNING(a/k/a RUNNABLE)(運行中):線程正在運行,虛擬機必須暫停(掛載)所有的線程去轉存堆棧信息,所以我們通常不可能見到正在進行堆棧轉存以外的任何一個線程。所謂堆棧轉存就是將程序線程的執行狀態通過dump文件保存或打印。
  • WAIT(等待):線程調用了wait()方法,正在耐心等待被喚醒。
  • TIMED_WAIT-:線程調用了wait()方法,並且指定了超時時限,Thread.sleep()就是通過限時等待實現的。
  • MONITOR(監聽):線程被一個監聽鎖阻塞了,並試圖進入一個“synchronized”塊。
  • NATIVE(本地):線程正在執行本地代碼,虛擬機不會暫停(掛載)本地線程,除非他們是用JNI調用的。
  • VMWAIT(虛擬機等待):線程被阻塞並獲取一個虛擬機資源,比如內部的互斥,或者正在等待其它事情要做,比如編譯器和GC線程。
  • SUSPENDED(暫停):線程可執行,但是已經被暫停了,正如前面提到的,堆棧轉存在專業他們的堆棧時會暫停所有的線程,所以我們的繁忙的線程通常會以這種形式出現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章