改進了jvm虛擬機器
Dalvik 基於寄存器,而
JVM 基於棧。基於寄存器的虛擬機器對於更大的程式來說,在它們編譯的時候,花費的時間更短。
Dalvik 和Java 運行環境的區別
[2]1:Dalvik主要是完成對象生命週期管理,堆棧管理,線程管理,安全和異常管理,以及記憶體回收等等重要功能。
2:Dalvik負責進程隔離和線程管理,每一個Android應用在底層都會對應一個獨立的Dalvik虛擬機器執行個體,其代碼在虛擬機器的解釋下得以執行。
3:不同於Java虛擬機器運行java位元組碼,Dalvik虛擬機器啟動並執行是其專有的檔案格式Dex
4:dex檔案格式可以減少整體檔案尺寸,提高I/o操作的類尋找速度。
5:odex是為了在運行過程中進一步提高效能,對dex檔案的進一步最佳化。
6:所有的Android應用的線程都對應一個Linux線程,虛擬機器因而可以更多的依賴作業系統的線程調度和管理機制
7:有一個特殊的虛擬機器進程Zygote,他是虛擬機器執行個體的孵化器。它在系統啟動的時候就會產生,它會完成虛擬機器的初始化,庫的載入,預製類庫和初始化的操作。如果系統需要一個新的虛擬機器執行個體,它會迅速複製自身,以最快的資料提供給系統。對於一些唯讀系統庫,所有虛擬機器執行個體都和Zygote共用一塊記憶體地區。
其實其核心的差異,就是Dalvik 虛擬機器架構是 register-based,與 Sun JDK 的 stack-based 不同,也就是架構上的差異。我先摘錄幾段網上可以找到的資料,重新整理和排版了一下,由於這些資料在網上經過多次轉載,轉寄和加工,原作者不詳,所以無法標註其原作者們,如有原作者認領或者質疑,請及時通知我。
(1) Dalvik VM和JVM 的第一個區別是 Dalvik VM是基於寄存器的架構(reg based),而JVM是棧機(stack based)。reg based VM的好處是可以做到更好的提前最佳化(ahead-of-time optimization)。
另外reg based的VM執行起來更快,但是代價是更大的代碼長度。
(2) 另外一個區別是Dalvik可以允許多個instance 運行,也就是說每一個Android 的App是獨立跑在一個VM中.這樣做的好處是一個App crash只會影響到自身的VM,不會影響到其他。 Dalvik的設計是每一個Dalvik的VM都是Linux下面的一個進程。那麼這就需要高效的IPC。另外每一個VM是單獨啟動並執行好處還有可以動態active/deactive自己的VM而不會影響到其他VM
(3) 接下來就是關於著作權之類爭論。(可以參看下面文章)
既然reg based VM有那麼多好處,為什麼之前設計JAVA的人沒有採用reg based而是採用stack based的呢? 原來stack based的VM也有其優點,就是它不對host平台的reg數量做假設,有利於移植到不同的平台。而Dalvik則不關心這些,因為它本來就是為ARM這樣的多reg平台設計的。另外Dalvik被移植到x86也說明,即使是x86這種reg很少的平台,reg
based的VM也是沒有問題的。
下面著重說下DVM的優勢:(部分文字我加黑以突出)
1、在編譯時間提前最佳化代碼而不是等到運行時
2、 虛擬機器很小,使用的空間也小;被設計來滿足可高效運行多種虛擬機器執行個體。
3、常量池已被修改為只使用32位的索引,以 簡化解譯器
JVM 的位元組碼主要是零地址形式的,概念上說JVM是基於棧的架構。Google Android平台上的應用程式的主要開發語言是Java,通過其中的Dalvik VM來運行Java程式。為了能正確實現語義,Dalvik VM的許多設計都考慮到與JVM的相容性;但它卻採用了基於寄存器的架構,其位元組碼主要是二地址/三地址的混合形式。
基於棧與基於寄存器的 架構,誰更快?現在實際的處理器,大多都是基於寄存器的架構,從側面反映出基於寄存器比基於棧的架構更與實際的處理器接近。但對於VM來說,源架構的求值
棧或者寄存器都可能是用實際機器的記憶體來類比的,所以效能特性與實際硬體又有不同。一般認為基於寄存器架構的Dalvik VM比基於棧架構JVM執行效率更高,原因是:雖然零地址指令更緊湊,但完成操作需要更多的load/store指令,也意味著更多的指令指派 (instruction dispatch)次數與記憶體訪問次數;訪問記憶體是執行速度的一個重要瓶頸,二地址或三地址指令雖然每條指令占的空間較多,但總體來說可以用更少的指令完 成操作,指令指派與記憶體訪問次數都較少。
我們從下面的可以明了的看到與同一段Java代碼對應的Java bytecode 與Dalvid bytecode的比較:網上一些文章在討論
Dalvik 時,大都簡單提及 Dalvik 執行速度比 JVM 快,但移植性稍差。這裡 我們延伸探討一下。在一個解譯器上執行 VM 指令,包含三個步驟,指令指派、訪問運算元和執 行計算。 指令指派(Instructions dispatch)負責從記憶體中讀取 VM 指令,然後跳轉到相應的解譯器代碼 指令指派 中。上面提到過,完成同樣的事情,基於棧的虛擬機器需要更多的指令,意味著更多的指令指派和 記憶體訪問次數,這是 JVM 的執行效能不如 Dalvik VM 的原因之一。
訪問運算元 訪問運算元(Operands access)是指讀取和寫回源運算元和目的運算元。Dalvik VM 通過虛擬 運算元 寄存器來訪問運算元, 由於具有相近的血緣, Dalvik 的虛擬寄存器在映射到物理寄存器方面具有 更充分的優勢, 這也是 Dalvik
VM 效能較佳的一個原因。 JVM 的運算元通過運算元棧來訪問, 而 因為指令中沒有使用任何通用寄存器,在虛擬機器的實現中可以比較自由的分配實際機器的寄存 器,因而可移植性高。作為一個最佳化,運算元棧也可以由編譯器映射到物理寄存器上,減少資料 移動的開銷。 指令執行(Instructions compute)這個似乎沒什麼可解釋的,老老實實執行就行。 指令執行
一個應用中會定義很多類, 編譯完成後即會有很多相應 的CLASS檔案,CLASS檔案 間會有不少冗餘的資訊。
dex位元組碼和標準Java的位元組碼(Class)在結構上的一個區別是dex位元組碼將多個檔案整合成一個,這樣,除了減少整體的檔案尺寸,I/O操作,也提高了類的尋找速度。
原來每個類檔案中的常量池現在由DEX檔案中一個常量池來管理。
DEX檔案可以進行進一步最佳化。最佳化主要是針對以下幾個方面:
1、調整所有欄位的位元組序(LITTLE_ENDIAN)和對齊結構中的沒一個域
2、驗證DEX檔案中的所有類
3、對一些特定的類進行最佳化,對方法裡的作業碼進行最佳化
最佳化 最佳化後的檔案大小會有所增加,應該是原DEX檔案的1-4倍。 odex是為了在運行過程中進一步提高效能,對dex檔案的進一步最佳化
每一個Android應用都運行在一個Dalvik虛擬機器執行個體裡,而每一個虛擬機器執行個體都是一個獨立的進程空間。每個進程之間可以通訊(IPC,Binder機制實現)。虛擬機器的線程機制,記憶體配置和管理,Mutex等等都是依賴底層作業系統而實現的。
不同的應用在不同的進程空間裡運行,當一個虛擬機器關閉或意外中止時不會對其它虛擬機器造成影響,可以最大程度的保護應用的安全和獨立運行。
Zygote是虛擬機器執行個體的孵化器。AndroidRuntime.cpp中ZygoteInit.main()的執行會完成一個分裂,分裂出來的子進程繼續初始化Java層的架構,這個分裂出來的進程就是system_server。每當系統要求執行一個Android應用程式,Zygote就會FORK出一個子進程來執行該應用程式。這樣做的好處顯而易見:Zygote進程是在系統啟動時產生的,它會完成虛擬機器的初始化,庫的載入,預置類庫的載入和初始化等等操作,而在系統需要一個新的虛擬機器執行個體時,Zygote通過複製自身,最快速的提供個系統。另外,對於一些唯讀系統庫,所有虛擬機器執行個體都和Zygote共用一塊記憶體地區,大大節省了記憶體開銷。
=============================分割線===========================
下面我以我的認知來簡單總結描述一下,DVM和JVM這種架構上的差異所產生的影響
JVM其核心目的,是為了構建一個真正跨OS平台,跨指令集的程式運行環境(VM)。DVM的目的是為了將android OS的本地資源和環境,以一種統一的介面提供給應用程式開發。嚴格來說,DVM不是真正的VM,它只是開發的時候提供了VM的環境,並不是在啟動並執行時候提供真正的VM容器。這也是為什麼JVM必須設計成stack-based的原因。
JVM:所有的jar程式,其運行環境完全是由JVM來提供,包括運行時,各類資源的調度,而JVM的架構,其設計為一個JVM裡面可以運行多個java程式,JVM就像一個真正的“機器”,可以跑著多個程式。如果去看看一些企業級的JVM(例如tom cat,WAS),從OS的進程管理中,一般你只能看見一個JVM的進程(當然,你也可以起多個JVM,但JVM架構就是OS-JVM-APP的3層運行時模式),而看不見JVM裡面啟動並執行程式,而一個JVM裡,可以跑多個java
app。簡單得說,JVM完全屏蔽了應用程式和OS之間的聯絡,而改用JVM充當了中介層,這也是一個真正跨平台運行時VM必須要做到的。只要是相同的JDK,JVM為所有在其中啟動並執行程式,提供了完全一致的運行環境,而不論你是什麼樣的底層OS和硬體條件。因此這也是我在其他一篇答案中提到,JVM的特點是取底層OS和硬體環境的交集,從而保障這種一致性。而所有應用程式和底層資源的互動,一定是依賴JVM的傳遞和轉換來實現。JVM真正實現了一個OS對應用程式運行時管理的所有功能。從開發環境角度和運行時角度,都是完全一致的真正VM
DVM:而DVM的特點在於使用了Zygote,Zygote有幾個非常有意思的特點。
一是Zygote採用預先載入,由其首先判定安裝的APK的需要以及相互依存樹,以及OS及硬體環境的特點,在每次啟動的時候進行預先載入(現在你明白為什麼android的app在應用管理裡你能輕易查到它都調用了那些關鍵性的本地資源的原因了吧?),這就意味著,你安裝的應用越多,Zygote的載入就越慢,一般來說你的手機啟動就會越慢。另外來說,在不同的硬體環境裡(例如有無GPS晶片)Zygote初始化的執行個體是不同的。也就是說,zygote並不提供一個統一的運行環境,具有更好的彈性,這種機制意味著DVM可以取底層資源的合集來提供上層應用使用,差別只是在程式安裝或者啟動的過程中,DVM可以提示程式需求資源,本地環境可能未能滿足而導致無法運行。DVM的Zygote並不是提供一個運行時容器,它提供的只是一個用於共用的進程,所有的應用程式運行,都是獨立的,OS層級的進程,直接受到OS層面的資源控制以及調度的影響,只是他們共用Zygote說預先載入的類而已。這也就是我為什麼說,DVM就像是給每個應用程式在底層加了個套子,而不是提供了一個真正的運行時的VM。也就是說,DVM在開發環境中說提供的VM平台,和運行時的環境是很有可能不一致的。開發環境中提供的VM平台,是一個各種運行時可能環境的合集。
從這點上來說,一般我們認為,JVM中的JAVA程式的崩潰,最多導致JVM的崩潰,而不會導致OS崩潰,但是apk的崩潰,可以直接導致OS崩潰,android手機會因為應用程式死機,大家應該是很常見了。但是大家一般是不會看到java程式導致死機吧?因為運行時中間隔著一個JVM。(當然,其實還是有些小門道可以用java程式讓OS崩潰,因為這個,我和某些JAVA大拿打賭贏過飯局,呵呵,不過這是其他話題,不在這裡展開了)
除此之外,在JVM的機制中,不同的程式,打包以後,他們都是在運行層級真正獨立的程式(指程式應用他們相互之間的關係,而不是和JVM的關係),即便他們在包裡使用了同樣的類,運行時都是單獨載入,單獨啟動並執行(及載入多遍)。
DVM這種預先載入-共用的機制,使得不同應用之間,在運行時,是共用相同的類的,一般來說,在系統資源消耗方面,擁有更高的效率。
最後,補充一點,byte code並不意味著就是解釋執行,也能是載入編譯,安裝編譯,先行編譯等等。實際上,不同的byte code的程式,不同的技術,不同的具體語言,其真正執行的情況是挺複雜,難以一概而論的,好多都是混合技術的案例,從我對odex的技術來看,就是個典型案例。當然這是題外話,不多展開了