Android運行時ART簡要介紹和學習計劃

來源:互聯網
上載者:User

Android運行時ART簡要介紹和學習計劃

Android在4.4就已推出新運行時ART,準備替代用了有些時日的Dalvik。不過當時尚屬測試版,主角仍是Dalvik。 直到今年的Google I/O大會,ART才正式取代Dalvik。這個訊息在科技界引起不小轟動,也吸引不少技術人員對它的“技術分析”。可惜這些“技術分析”不過是引用了官方的資料和圖表而已。這一系列文章將對ART進行真正的技術分析。老規矩,分析前先進行簡要介紹和制定學習計劃。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

ART的發布之所以引起大家的關注,是因為Andoid與iOS相比,一直被人詬病它的流暢性。Android的流暢性問題,有一部分原因就歸結於它的應用程式和部分系統服務是運行虛擬機器之上的,也就是運行在Dalvik虛擬機器之上,而iOS的應用程式和系統服務都是直接執行本地機器指令的。除了使用ART替換Dalvik之外,我們也應當看到,Android從3.0開始,就不遺餘力地改進系統的流暢性。例如,3.0增加了對應用程式2D UI的硬體加速渲染,也就是GPU渲染。在此之前,應用程式的2D UI一直都是使用軟體渲染,也就是CPU渲染。又如4.1通過Project Butter,在UI架構中引入了VSYNC、Triple Buffer和HWComposer等技術,極大地提高UI的流暢性。

ART之所以會比Dalvik快,是因為ART執行的是本地機器指令,而Dalvik執行的是Dex位元組碼,通過通過解譯器執行。儘管Dalvik也會對頻繁執行的代碼進行JIT產生本地機器指令來執行,但畢竟在應用程式啟動並執行過程中將Dex位元組碼翻譯成本地機器機器指令也會影響到應用程式本身的執行,因此即使Dalvik使用了JIT,也在一定程度上也比不上直接就可以執行本地機器指令的運行時。

在前面Android ART運行時無縫替換Dalvik虛擬機器的過程分析一文中,我們提到,ART像Dalvik一樣,都實現Java虛擬機器介面,1所示:

圖1 Dalvik、ART和Java VM的關係

Zygote進程在啟動的過程中,正是通過圖1所示的介面建立Dalvik或者ART虛擬機器的,這樣看來,ART雖然執行的本地機器指令,但是它表面看來,又是一個不折不扣的虛擬機器。也正是因為這樣,ART才可以在不重新編譯APK的基礎上,直接可以載入和運行APK。這也是ART運行時可以無縫替換Dalvik運行時的原理。因此,我們就可以得出一個結論:ART是一個執行本地機器指令的虛擬機器。這個結論似乎有點矛盾,既然是執行本地機器指令,為什麼又稱為虛擬機器呢?從接下來的文章分析可以知道,ART除了實現Java虛擬機器介面之外,其內部還有垃圾收集機制,同時還有Java核心類庫調用,因此,隨著對ART的深入分析,我們就認為這個結論是不矛盾的了。

上面提到,ART才可以在不重新編譯APK的基礎上,直接對其進行載入和運行,這是由於APK在安裝時被執行了AOT。AOT(Ahead Of Time)是相對JIT(Just In Time)而言的。也就是在APK運行之前,就對其包含的Dex位元組碼進行翻譯,得到對應的本地機器指令,於是就可以在運行時直接執行了。這種技術不但使得我們可以不對原有的APK作任何修改,還可以使得這些APK只需要在安裝時翻譯一次,就可以無數次以本地機器指令的形式運行。這種技術與我們用C/C++語言編寫一個程式,然後用GCC編譯得到一個可執行程式,最後這個可執行程式就可以無數次地載入到系統執行,是差不多的。

在ART中,打包在APK裡面的Dex位元組碼是通過LLVM翻譯成本地機器指令的。LLVM是一個用來快速開發自己的編譯器的架構系統,關於它的介紹,可以參考它的作者之一Chris Lattner寫的這篇文章:http://www.aosabook.org/en/llvm.html。說起Chris Lattner,他就是Apple今年發布的Swift語言的首席架構師啊,所以我們就可以感受到LLVM有多強大了。總體來說,LLVM包含有三類組件,2所示:

圖2 LLVM組件

其中,前端(Frontend)對輸入的原始碼(Source Code)進行文法分析後,產生一棵抽象文法樹(Abstract Syntax Tree,AST),並且可以進一步將得到的抽象文法樹轉化一種稱為LLVM IR的中繼語言。LLVM IR是一種與程式設計語言無關的中繼語言,也就是說,不管是C語言,還是Fortran、Ada語言編寫的源檔案,經過文法分析後,最終都可以得到一個對應的LLVM IR檔案。這個LLVM IR檔案可以作為後面的最佳化器(Optimizer)和後端(Backend)的輸入檔案。最佳化器對LLVM IR檔案進行最佳化,例如消除代碼裡面的冗餘計算,以提到最終產生的程式碼的執行效率。後端負責產生最終的機器指令。

LLVM的上述架構大大簡化開發編譯器的流程,因為開發人員需要關注的僅僅是前端,然後就可以利用現成的最佳化器來進行代碼最佳化,並且利用現成的後端產生各種體繫結構相關的機器指令,3所示:

圖3 利用現成的與語言無關的最佳化器和後端為語言相關的前端產生各種體繫結構相關的機器指令

在圖3中,我們分別為C、Fortran和Ada三種語言開發三個不同的前端,然後利用現成的最佳化器對它們產生的LLVM IR語言進行最佳化,並且通過現成的後端產生X86、PowerPC和ARM三種不同體繫結構的機器指令。

如果我們沒有忘記,在Dalvik運行時中,APK在安裝的時候,安裝服務PackageManagerService會通過守護進程installd調用一個工具dexopt對打包在APK裡麵包含有Dex位元組碼的classes.dex進行最佳化,最佳化得到的檔案儲存在/data/dalvik-cache目錄中,並且以.odex為尾碼名,表示這是一個最佳化過的Dex檔案。在ART運行時中,APK在安裝的時候,同樣安裝服務PackageManagerService會通過守護進程installd調用另外一個工具dex2oat對打包在APK裡麵包含有Dex位元組碼進翻譯。這個翻譯器實際上就是基於LLVM架構實現的一個編譯器,它的前端是一個Dex文法分析器。翻譯後得到的是一個ELF檔案,這個ELF檔案同樣是以.odex尾碼結束,並且也是儲存在/data/dalvik-cache目錄中。

ELF是Linux系統使用的一種檔案格式,我們平時接觸的靜態庫、動態庫和可執行檔都是以這種格式儲存的,但是由dexoat工具產生的檔案與上述三種檔案都不一樣,它有兩個特殊的段oatdata和oatexec,分別用來儲存原來打包在APK裡面的dex檔案和翻譯這個dex檔案裡面的類方法得到本地機器指令,4所示:

圖4 ART翻譯classes.dex後得到的ELF檔案格式

在ELF檔案的動態段(dymanic section)中,還匯出了三個符號oatdata、oatexec和oatlastword,分別用來描述oatdata和oatexec段加段到記憶體後的起止地址。在oatdata段中,包含了兩個重要的資訊,一個資訊是原來的classes.dex檔案的完整內容,另一個資訊引導ART找到classes.dex檔案裡面的類方法所對應的本地機器指令,這些本地機器指令就儲存在oatexec段中。

舉個例子說,我們在classes.dex檔案中有一個類A,那麼當我們知道類A的名字後,就可以通過儲存在oatdata段的dex檔案得到類A的所有資訊,比如它的父類、成員變數和成員函數等。另一方面,類A在oatdata段中有一個對應的OatClass結構體。這個OatClass結構體描述了類A的每一個方法所對應的本地機器指令在oatexec段的位置。也就是說,當我們知道一個類及其某一個方法的名字(簽名)之後,就可以通過oatdata段的dex檔案內容和OatClass結構體找到其在oatexec段的本地機器指令,這樣就可以執行這個類方法了。

通過上面的分析,我們就將ART的運行原理都簡要地介紹了,總結如下:

1. 在Android系統啟動過程中建立的Zygote進程利用ART運行時匯出的Java虛擬機器介面建立ART虛擬機器。

2. APK在安裝的時候,打包在裡面的classes.dex檔案會被工具dex2oat翻譯成本地機器指令,最終得到一個ELF格式的檔案。

3. APK運行時,上述產生的ELF檔案會被載入到記憶體中,並且ART虛擬機器可以通過裡面的oatdata和oatexec段找到任意一個類的方法對應的本地機器指令來執行。

對於第1點,ART虛擬機器的建立過程中,可以參考前面Android ART運行時無縫替換Dalvik虛擬機器的過程分析一文。

對於第2點,APK裡面的Dex位元組碼被dex2oat工具翻譯生本地機器指令的過程,掌握它需要有紮實的編譯知識。由於知識、能力、時間有限,因此這一部分的內容我們就略過不分析了,但是這將不會影響我們對ART運行時的理解。

對於第3點,是我們理解ART運行時的關鍵所在。以ART虛擬機器的啟動過程為例,從前面Android ART運行時無縫替換Dalvik虛擬機器的過程分析一文可以知道,在AndroidRuntime類的成員函數start中,ART虛擬機器建立和初始化完成後,Zygote進程就會通過它匯出的JNI介面CallStaticVoidMethod使得它以指定的類方法為入口正式進入運行狀態,如下所示:

void AndroidRuntime::start(const char* className, const char* options)  {      ......            /* start the virtual machine */      JniInvocation jni_invocation;      jni_invocation.Init(NULL);      JNIEnv* env;      if (startVm(&mJavaVM, &env) != 0) {          return;      }            ......            /*      * Start VM.  This thread becomes the main thread of the VM, and will      * not return until the VM exits.      */      char* slashClassName = toSlashClassName(className);      jclass startClass = env->FindClass(slashClassName);      if (startClass == NULL) {          ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);          /* keep going */      } else {          jmethodID startMeth = env->GetStaticMethodID(startClass, "main",          "([Ljava/lang/String;)V");          if (startMeth == NULL) {              ALOGE("JavaVM unable to find main() in '%s'\n", className);              /* keep going */          } else {              env->CallStaticVoidMethod(startClass, startMeth, strArray);        #if 0              if (env->ExceptionCheck())                  threadExitUncaughtException(env);  #endif          }      }                ......  }  
這個函數定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。

在AndroidRuntime類的成員函數start中,參數className的值等於“com.android.internal.os.ZygoteInit”,本地變數env是從調用另外一個成員函數startVM建立的ART虛擬機器獲得的JNI介面。函數的目標就是要找到一個名稱為com.android.internal.os.ZygoteInit的類,以及它的靜態成員函數main,然後就以這個函數為入口,開始運行ART虛擬機器。為此,函數執行了以下步驟:

1. 調用JNI介面FindClass載入com.android.internal.os.ZygoteInit類。

2. 調用JNI介面GetStaticMethodID找到com.android.internal.os.ZygoteInit類的靜態成員函數main。

3. 調用JNI介面CallStaticVoidMethod開始執行com.android.internal.os.ZygoteInit類的靜態成員函數main。

注意,在第3步中,要執行的是CallStaticVoidMethod開始執行com.android.internal.os.ZygoteInit類的靜態成員函數main的本地機器指令,而不是Dex位元組碼。這樣就引出以下三個關鍵問題:

1. ART如何找到com.android.internal.os.ZygoteInit類?

2. ART如何找到com.android.internal.os.ZygoteInit類的靜態成員函數main?

3. ART如何找到com.android.internal.os.ZygoteInit類的靜態成員函數main的本地機器指令?

解決上述三個問題所需要的資訊都存在於dex2oat工具產生的ELF檔案中。因此,在接下來的文章中,我們將通過分析dex2oat工具產生的ELF檔案來回答上述三個問題,使得我們可以更加好地理解ART的工作原理。

如上所述,由於ART在尋找類方法時,需要用到儲存在ELF檔案的oatdata段的原dex檔案內容,實質上就是要對dex檔案進行解析,以獲得相關的資訊。這與Dalvik虛擬機器在dex檔案中尋找類和方法資訊的過程是一樣的。這意味著要理解ART運行時,必須先要理解Dalvik虛擬機器。Dalvik虛擬機器的相關知識可以參考Dalvik虛擬機器簡要介紹和學習計劃這個系列的文章。這告訴我們一個道理,舊的知識並沒有過時,它對我們學習新的知識是有協助的,有時候甚至是必須的。所以大家就不要覺得最前面寫的那些基於Android 2.3版本的文章是沒有用的了。我們在學習一樣新東西的時候,無論是新的知識,還是舊的知識,對我們理解它的原理,都是很有協助的!

我們除了要以dex2oat工具產生的ELF檔案作為切入點來分析ART運行時之外,還會結合ART運行時的垃圾收集機制來說明ART運行時與Dalvik虛擬機器一樣也是一個虛擬機器,以此加深對ART運行時的理解。與Dalvik虛擬機器的垃圾收集機制相比,ART運行時的垃圾收集機制更為複雜,由此帶來的垃圾收集效率也更高。因此我們在分析ART運行時的垃圾收集機制之前,先會分析Dalvik虛擬機器的垃圾收集機制。一方面是有利於我們循序漸進、由簡地繁地講解ART運行時的垃圾收集原理,另一方面也方便我們對比ART運行時和Dalvik虛擬機器的垃圾收集機制有哪些不同,從而可以更好地理解為ART運行時的垃圾收集效率更高。

綜上所述,接下來我們就按照以下幾個情景來分析ART的工作原理:

1. Dalvik虛擬機器載入Dex檔案,並且從中尋找類和方法的過程。

2. ART運行時尋找類方法的本地機器指令的過程。

3. Dalvik虛擬機器的垃圾收集過程。

4. ART運行時的垃圾收集過程。

以上情景將基於Android 4.4源碼進行分析,一方面是因為Android L版本的源碼還沒有放出來,另一方面是相信萬變不離其宗,即使Androd L版本的ART實現有變化,但是基本的原理還是一樣的。敬請關注!同時更多的資訊可以關注老羅的新浪微博:http://weibo.com/shengyangluo。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.