【轉】安卓Java的虛擬機區別


 

Google於2007年底正式發布了Android SDK, 作為 Android系統的重要特性,Dalvik虛擬機也第一次進入了人們的視野。它對內存的高效使用,和在低速CPU上表現出的高性能,確實令人刮目相看。 依賴於底層Posix兼容的操作系統,它可以簡單的完成進程隔離和線程管理。每一個Android應用在底層都會對應一個獨立的Dalvik虛擬機實例, 其代碼在虛擬機的解釋下得以執行。 
很多人認為Dalvik虛擬機是一個Java虛擬機,因為Android的編程語言恰恰就是Java語言。但是這種說法並不准確,因為Dalvik虛擬機並不是按照Java虛擬機的規范來實現的,兩者並不兼容;同時還要兩個明顯的不同:

  • Java虛擬機運行的是Java字節碼,而Dalvik虛擬機運行的則是其專有的文件格式DEX(Dalvik Executable)。
  • 在Java SE程序中的Java類會被編譯成一個或者多個字節碼文件(.class)然后打包到JAR文件,而后Java虛擬機會從相應的CLASS文件和JAR文 件中獲取相應的字節碼;Android應用雖然也是使用Java語言進行編程,但是在編譯成CLASS文件后,還會通過一個工具(dx)將應用所有的 CLASS文件轉換成一個DEX文件,而后Dalvik虛擬機會從其中讀取指令和數據。

Dalvik和Android系統Android作為新一代的基於Linux的開源手機操作系統,其系統架構由下而上可以分為以下幾部分:

  • Linux內核
  • 本地庫
  • Android運行庫
  • 應用框架
  • 應用

 

   java虛擬機和Dalvik虛擬機的區別:

  java虛擬機 Dalvik虛擬機
  java虛擬機基於。 基於棧的機器必須使用指令來載入和操作棧上數據,所需指令更多更多 dalvik虛擬機是基於寄存器
  java虛擬機運行的是java字節碼。(java類會被編譯成一個或多個字節碼.class文件,打包到.jar文件中,java虛擬機從相應的.class文件和.jar文件中獲取相應的字節碼)

 

Dalvik運行的是自定義的.dex字節碼格式。(java類被編譯成.class文件后,會通過一個dx工具將所有的.class文件轉換成一個.dex文件,然后dalvik虛擬機會從其中讀取指令和數據)
    常量池已被修改為只使用32位的索引,以 簡化解釋器。dalvik的堆和棧的參數可以通過-Xms和-Xmx更改
    一個應用,一個虛擬機實例,一個進程(所有android應用的線程都是對應一個linux線程,都運行在自己的沙盒中,不同的應用在不同的進程中運行。每個android dalvik應用程序都被賦予了一個獨立的linux PID(app_*))

 

 

 

Dalvik和標准Java虛擬機(JVM)之間的首要差別之一,就是Dalvik基於寄存器,而JVM基於棧。
Dalvik和Java之間的另外一大區別就是運行環境——Dalvik經過優化, 允許在有限的內存中同時運行多個虛擬機的實例,並且每一個 Dalvik應用作為一個獨立的Linux進程執行。
(1)虛擬機很小,使用的空間也小;
(2)Dalvik沒有 JIT編譯器;
(3)常量池已被修改為只使用32位的索引,以簡化解釋器;
(4)它使用自己的字節碼,而非Java字節碼。

 

 

Dalvik虛擬機架構:

     在android源碼中,Dalvik虛擬機的實現位於“dalvik/”目錄下,其中“dalvik/vm”是虛擬機的實現部分,將會編譯成libdvm.so;而"dalvik/libdex"將會編譯成libdex.a靜態庫作為dex工具;“dalvik/dexdump”是.dex文件的反編譯工具;虛擬機的可執行程序位於“dalvik/dalvikvm”中,將會編譯成dalvikvm可執行文件。

  dalvik虛擬機架構:

Android應用編譯及運行流程:

 Dalvik進程管理:

         dalvik進程管理是依賴於linux的進程體系結構的,如要為應用程序創建一個進程,它會使用linux的fork機制來復制一個進程(復制進程往往比創建進程效率更高)。

         Zygote是一個虛擬機進程,同時也是一個虛擬機實例的孵化器,它通過init進程啟動。首先會孵化出System_Server(android絕大多系統服務的守護進程,它會監聽socket等待請求命令,當有一個應用程序啟動時,就會向它發出請求,zygote就會FORK出一個新的應用程序進程).每當系統要求執行一個android應用程序時,Zygote就會運用linux的FORK進制產生一個子進程來執行該應用程序。

 

 JVM和Dalvik進程管理:

 

        linux中進程間通信的方式有很多,但是dalvik使用的是信號方式來完成進程間通信。

 

Android的初始化流程

 

實際上.dex文件就是把多個class文件中的常量、方法等放到一起。形成如上圖所示的結構。

 在架構上jvm是基於棧的架構,所以每次訪問數據cpu都要到內存中取到數據。

而dalvik是基於寄存器的架構。寄存器是在cpu上的一塊存儲空間,cpu如果直接從寄存器上讀取數據的話就會快很多。

【以下也是轉】

(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的技術來看,就是個典型案例。當然這是題外話,不多展開了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM