Android 虛擬機簡單介紹——ART、Dalvik、啓動流程分析

Android 虛擬機方面的知識,我是經過《深刻理解 Android 內核設計思想》來學習的,內容特別多(只有一章,但有 160 頁),感受和 Android 開發有些偏,所以不少內容都沒有認真去看,好比 EFL 格式等,這裏只是選取了一些感受比較重要的作一個大體的簡單的介紹。java

虛擬機基礎知識
Java VM
詳見《深刻理解 Java 虛擬機》android

LLVM
LLVM 全稱是 Low Level Virtual Machine,但和虛擬機沒太大關係,官方定義是:The LLVM Project is a colection of modular and resuable compiler and toolchain technologies。即 LLVM 的價值在於可模塊化、可重複使用。算法

LLVM 框架以下所示:安全

Frontend:負責分析源代碼、檢查錯誤,而後將源碼編譯成抽象語法樹app

Optimizer:經過多種優化手段來提升代碼的運行效率,在必定程度上獨立於具體的語言和目標平臺框架

Backend:也被稱爲代碼生成器,用於將前述的源碼轉化爲目標前臺的指令集socket

LLVM 的模塊化:模塊化

能夠看出,若是要讓基於 LLVM 框架的編譯器支持一種新語言,那麼所要作的可能僅僅是實現一個新的 Frontend,而已有的 Optimizer 和 Backend 則能作到重複使用。上述能力獲得實現的關鍵在於 LLVM 的 Intermediate Representation(IR),IR 能在 LLVM 的編譯器中(具體在 Optimizer 階段)以一種相對獨立的方式來表述各類源代碼,從而很好地剝離了各類不一樣語言間的差別,進而實現模塊的複用。函數

LLVM 和 Android 的關係能夠參考 RednaxelaFX 大神的一個回答:Android 中的 LLVM 主要作什麼?工具

簡單來講,就是從 Jellybean MR1 版本開始,Google 將 LLVM 做爲 Android 工具鏈和 Android NDK 的替代編譯器,能夠用於編譯在 Android 應用程序中的 C/C++ 代碼。

Android 中的經典垃圾回收算法
Android 中不論是 Dalvik 仍是 Art,它們所使用的垃圾回收算法都是基於 Mark-Sweep 的。

GC 的觸發時機有:

GC_FOR_MALLOC。堆內存已滿的狀況下程序嘗試去分配新的內存塊

GC_CONCURRENT,堆內存超過特定閾值,觸發並行的 GC 事件

GC_HPROF_DUMP_HEAP,開發者主動請求建立 HPROF

GC_EXPLICIT,程序主動調用 gc() 函數,儘可能避免這種用法

Art 和 Dalvik 之爭
Dalvik 是 Android 4.4 以前的標準虛擬機,爲了性能上的考慮,Dalvik 所作出的努力有:

多個 Class 文件融合進一個 Dex 文件中,以節省內存空間

Dex 文件能夠在多個進程之間共享

在應用程序運行以前完成字節碼的檢驗操做,由於檢驗操做十分耗時

優化字節碼

多個進程共享的代碼不能隨意編輯,這是爲了保證安全性

但 Android 從誕生起就揹負了「系統龐大,運行慢」的包袱,所以,從 Android 4.4 開始,Art 就以和 Dalvik 暫時共存的形式正式進入了人們的視野,而在 Android Lollipop 中正式取代了 Dalvik 的位置。

Art 相比 Dalvik 在性能上有着顯著的優點,主要緣由在於 Dalvik 虛擬機多數狀況下還得經過解釋器的方式來執行 Dex 數據(JIT 雖然能在必定程度上提升效率,但也僅僅是針對一小部分狀況,做用有限);而 Art 虛擬機則採用了 AOT(Ahead Of Time) 技術,從而大幅提升了性能。

Dalvik 中的 JIT只有在程序運行過程當中纔會將部分熱點代碼編譯成機器碼,這在某種程度上也加劇了 CPU 的負擔。而 AOT 則會提早將 Java 代碼翻譯成針對目標平臺的機器碼,雖然這也意味着編譯時間有所增長,但 Android 系統的構建本來就慢,因此這點犧牲仍是值得的。

Art 虛擬機總體框架
不管是 Dalvik 仍是 Art,或者將來可能出現的新型虛擬機,它們提供的功能將所有封裝在一個 so 庫中,而且對外須要暴露 JNI_GetDefaultVMInitArgs、JNI_CreateVM 和 JNI_GetCreatedJavaVMs 三個接口,使用者(好比 Zygote)只須要按照統一的接口標準就能夠控制和使用全部類型的虛擬機了。

組成 Android 虛擬機的核心自系統包括但不限於 Runtime、ClassLoader System、Execution、Engine System、Heap Manager 和 GC 系統、JIT、JNI 環境等。

和標準的 JVM 同樣,類加載器在 Android 虛擬機中也扮演者很重要的做用,能夠分爲 Boot ClassLoader、System ClassLoader、Dex ClassLoader 等,全部被加載的類和它們的組成元素都將由 ClassLinker 作統一的管理。

除了字節碼解釋執行的方式,Art 還支持經過 AOT 來直接執行字節碼編譯而成的機器碼。AOT 的編譯時機有兩個:隨 Android ROM 構建時一塊兒編譯、程序安裝時執行編譯(針對第三方應用程序)。Art 引入了新的存儲格式,即 OAT 文件來存儲編譯後的機器代碼。而 OAT 機器碼的加載須要用到 ELF 的基礎能力。

另外,因爲一股腦地在程序安裝階段將 Dex 轉化爲 OAT 形成形成了必定的資源浪費,從 Android N 版本開始,Art 又改變了以前的 OAT 策略——程序在安裝時再也不統一執行 dex2oat,而改由根據程序的實際運行狀況來決定有哪些部分須要被編譯成本地代碼,即恢復了 Interpreter、JIT、OAT 三足鼎立的局面。一方面,這種新變化大幅加快了程序的安裝速度,解決了系統更新時用戶須要經歷漫長等待的問題;另外一方面,因爲程序的首次啓動必須經過解釋器來運行,Android N 版本必須採用多種手段(新的解釋器,將 Verification 前移等)來保證程序的啓動速度不受影響。

應用程序除了解釋執行外,還會在運行過程當中實時作 JIT 編譯——不過它的結果並不會被持久化。另外,虛擬機會記錄下應用程序在動態運行過程當中被執行過的函數,並輸出到 Profile 文件裏。

AOT compile daemon 將在系統同時知足 idle 和充電狀態兩個條件時纔會被喚醒,並按照必定的邏輯來遍歷執行應用程序的 AOT 優化。因爲參與 AOT 的函數數量一般只佔應用程序代碼的一小部分,因此總體而言 Android N 版本 AOT 結果所佔用的空間大小比舊版本要小不少。

Dex 字節碼
Java 類文件和 DEX 文件對比:

ELF 文件格式
Art 虛擬機最大的特色就是經過 dex2oat 將 Dex 預編譯爲包含了機器指令的 oat 文件,從而顯著提高了程序的執行效率。而 oat 文件自己是基於 Linux 的可執行文件格式——ELF 所作的擴展。

ELF 文件至少支持 3 中形態:可重定向文件(Relocatable File)、可執行文件(Executable File)、可共享的對象文件(Shared Object File)。

Relocatable File 的一個具體範例是 .o 文件,它是在編譯過程當中產生的中間文件。

Shared Object File 即動態連接庫,一般以 「.so」 爲後綴名。

靜態連接庫的特色是會在程序的編譯連接階段就完成函數和變量的地址解析工做,並使之成爲可執行程序中不可分割的一部分。

動態連接庫不須要在編譯時就打包到可執行程序中,而是等到後者在運行階段在實現動態的加載和重定位。動態連接庫在被加載到內存中以後,操做系統須要爲它執行動態鏈接操做。

動態連接庫的處理過程以下:

在編譯階段,程序經歷了預編譯、編譯、彙編及連接操做後,最終造成一個 ELF 可執行程序。同時程序所依賴的動態庫會被記錄到 .dynamic 區段中;加載動態庫所需的 Linker 由 .interp 來指示。

程序運行起來後,系統首先會經過 .interp 區段找到鏈接器的絕對路徑,而後將控制權交給它

Linker 負責解析 .dynamic 中的記錄,得出程序依賴的全部動態連接庫

動態連接庫加載完成後,它們所包含的 export 函數在內存中的地址就能夠肯定下來了,Linker 經過預設機制(如 GOT)來保證程序中引用到外部函數的地方能夠正常工做,即完成 Dynamic Relocation

OAT
與 OAT 相關的文件格式後綴有幾種:

.art,這個文件也被稱爲 image,由 dex2oat 工具生成,它的內部包含了不少 Dex 文件,內部存儲的是加載好的class信息以及一些事先建立好的對象,Zygote 在啓動過程當中會加載 boot.art

.oat,由 dex2oat 工具生成,boot.oat 內部存儲的是代碼

.odex,在 Dalvik 中,.odex 表示被優化後的 Dex 文件;Art 中也一樣存在 odex 文件,但和 Dalvik 中的狀況不一樣

應用程序的安裝流程:

Android 虛擬機的典型啓動流程
Android 虛擬機啓動的大體流程圖以下:

下面分析具體的代碼執行過程,首先看腳本 init.rc 與 zygote 相關的內容:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
1
2
3
4
5
6
7
app_process 是 zygote 的載體,其 main 函數以下:

int main(int argc, const char* const argv[])
{
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        ...
        runtime.start("com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    } else {
        ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
runtime 指 AndroidRuntime:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL); // 初始化當前的運行環境
    
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) { // 啓動虛擬機
        return;
    }
    
    onVmCreated(env); // 虛擬機建立成功,執行回調函數通知調用者
    
    /*
     * 註冊 native 函數
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    
    /*
     * 開始執行目標對象的主函數
     */
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        // 執行 main 函數
        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
        }
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
在啓動虛擬機以前,須要初始化當前的運行環境,具體是由 JniInvocation::Init 實現的:

bool JniInvocation::Init(const char* library) {
    library = GetLibrary(library, buffer); // 獲取虛擬機動態連接庫的名稱

    handle_ = dlopen(library, kDlopenFlags); // 打開虛擬機動態連接庫

    ...

    // 分別查找 VM 庫中的 3 個重要接口實現
    // 不管是 ART,仍是 Dalvik,或者將來的其它虛擬機都須要實現這 3 個接口,它們是統一的接口標準
    // zygote 經過這 3 個接口控制 Android 的虛擬機
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
                  "JNI_GetDefaultJavaVMInitArgs")) {
        return false;
    }
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
                  "JNI_CreateJavaVM")) {
        return false;
    }
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
                  "JNI_GetCreatedJavaVMs")) {
        return false;
    }
    
    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如今能夠啓動和初始化虛擬機了,核心工做是在 startVm 中完成的:

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    ...
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
能夠看到,它調用了 JniInvocation::Init 中找到的 JNI_CreateJavaVM 接口,該接口用於建立一個虛擬機:

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  ...
  
  RuntimeOptions options;
  for (int i = 0; i < args->nOptions; ++i) { // 解析全部的虛擬機選項
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
  }
  
  bool ignore_unrecognized = args->ignoreUnrecognized;
  
  if (!Runtime::Create(options, ignore_unrecognized)) { // 建立一個 Runtime 運行環境
    return JNI_ERR;
  }
  
  
  android::InitializeNativeLoader();
  Runtime* runtime = Runtime::Current();
  bool started = runtime->Start(); // 經過 Runtime 來啓動其管理的虛擬機
  if (!started) { // 啓動失敗
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }
  
  *p_env = Thread::Current()->GetJniEnv();
  *p_vm = runtime->GetJavaVM(); // 得到已經啓動完成的虛擬機示例,並傳遞給調用者
  
  return JNI_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Runtime::Start 成功啓動了一個虛擬機後,會經過 Init 函數來初始化:

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {

  ...
  
  heap_ = new gc::Heap(...); // 建立堆管理對象
  
  ...
  
  java_vm_ = JavaVMExt::Create(this, runtime_options, &error_msg);  // 建立 Java 虛擬機對象
  
  
  Thread::Startup();
  Thread* self = Thread::Attach("main", false, nullptr, false); // 主線程 attach
  
  if (UNLIKELY(IsAotCompiler())) {
    class_linker_ = new AotClassLinker(intern_table_);
  } else {
    class_linker_ = new ClassLinker(intern_table_);
  }
  
  if (GetHeap()->HasBootImageSpace()) { // 當前 Heap 是否包含 Boot Image(好比 boot.art)
    bool result = class_linker_->InitFromBootImage(&error_msg);
    ...
  } else {
    
    if (runtime_options.Exists(Opt::BootClassPathDexList)) {
      boot_class_path.swap(*runtime_options.GetOrDefault(Opt::BootClassPathDexList));
    } else {
      OpenDexFiles(dex_filenames,
                   dex_locations,
                   runtime_options.GetOrDefault(Opt::Image),
                   &boot_class_path);
    }
    
    instruction_set_ = runtime_options.GetOrDefault(Opt::ImageInstructionSet);
    
    if (!class_linker_->InitWithoutImage(std::move(boot_class_path), &error_msg)) {
      LOG(ERROR) << "Could not initialize without image: " << error_msg;
      return false;
    }
    // TODO: Should we move the following to InitWithoutImage?
    SetInstructionSet(instruction_set_);
    ...
  }
  
  return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
總結:

Android 虛擬機是 init.rc 被解析的時候啓動的

init.rc 在解析 zygote 的時候,調用了 AndroidRuntime 的 start 方法來啓動 Android 虛擬機

AndroidRuntime 首先經過 JniInvocation::Init 初始化運行環境,找到 JNI_GetDefaultVMInitArgs、JNI_CreateVM 和 JNI_GetCreatedJavaVMs 三個標準接口

接着執行 startVm,這個方法成功執行後,Android 虛擬機就被真正啓動了

startVm 內部調用以前找到的標準接口之一 JNI_CreateVM

JNI_CreateVM 內部建立了一個 Runtime 運行環境,並經過 Runtime 啓動了虛擬機

虛擬機成功啓動後,調用了 Runtime::Init 方法,用於初始化虛擬機,包括:建立 Java 虛擬機、建立 Heap 堆管理對象、加載主線程等

最後,經過 onVmCreated 方法通知虛擬機已經成功建立 ———————————————— 版權聲明:本文爲CSDN博主「zouzhiheng」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/u011330638/article/details/82830027

相關文章
相關標籤/搜索