ART虛擬機(jī)中的OAT文件及加載

OAT文件內(nèi)容

在 Android 7.0 (Nougat) 及以后版本中,ART 的混合編譯模式改變了 OAT 文件的結(jié)構(gòu)。以下是關(guān)鍵點(diǎn)的詳細(xì)解釋:

  1. OAT 文件內(nèi)容

    • 包含 AOT 編譯的機(jī)器碼:部分經(jīng)過 AOT 編譯的方法會(huì)以原生機(jī)器碼形式存在。由于這些代碼已經(jīng)是目標(biāo)平臺(tái)的機(jī)器碼,因此在運(yùn)行時(shí)無需再進(jìn)行字節(jié)碼到機(jī)器碼的轉(zhuǎn)換,可以直接執(zhí)行,從而顯著提高了啟動(dòng)速度和運(yùn)行效率。
    • 保留原始 DEX 字節(jié)碼:未編譯的方法會(huì)保留原始的 DEX 字節(jié)碼(并非完整 DEX 文件,而是按需嵌入)。
    • 元數(shù)據(jù)和映射信息:記錄哪些方法已編譯、哪些未編譯,以及兩者的對(duì)應(yīng)關(guān)系。
  2. 混合編譯邏輯

    • 安裝時(shí)部分 AOT:系統(tǒng)會(huì)根據(jù)設(shè)備狀態(tài)(如充電、空閑)選擇性地編譯常用方法。
    • 運(yùn)行時(shí) JIT:執(zhí)行未編譯的方法時(shí)觸發(fā) JIT 編譯(對(duì)于頻繁執(zhí)行的熱點(diǎn)代碼),結(jié)果緩存到內(nèi)存(后可能持久化為 AOT)。
    • Profile-Guided 優(yōu)化:根據(jù)用戶實(shí)際使用模式(記錄在 profile 文件中)逐步優(yōu)化高頻代碼。
    • 當(dāng)設(shè)備處于空閑且充電狀態(tài)時(shí),ART 的編譯守護(hù)進(jìn)程(dex2oat)會(huì)讀取配置文件,將熱點(diǎn)代碼進(jìn)行 AOT 編譯,并將編譯結(jié)果保存到 .odex 文件中
  3. 文件結(jié)構(gòu)示例

    OAT 文件
    ├── 已編譯方法 (機(jī)器碼)
    ├── 未編譯方法 (DEX 字節(jié)碼片段)
    ├── 方法調(diào)用跳轉(zhuǎn)表
    └── 元數(shù)據(jù) (記錄編譯狀態(tài))
    
  4. 版本演進(jìn)

    • Android 7.0 引入混合模式,解決純 AOT 導(dǎo)致的安裝時(shí)間過長和存儲(chǔ)占用問題。
    • Android 8.0 進(jìn)一步優(yōu)化 JIT 緩存持久化(通過 dex2oat 后臺(tái)任務(wù))。
  5. 開發(fā)者影響

    • 冷啟動(dòng)性能:首次執(zhí)行未編譯方法會(huì)有 JIT 開銷。
    • 熱代碼路徑:應(yīng)盡量保持關(guān)鍵方法簡(jiǎn)潔,便于優(yōu)化。
    • 調(diào)試信息:可通過 adb shell cmd package compile 查看編譯狀態(tài)。

因此,OAT 文件實(shí)質(zhì)是一個(gè)分層容器,既包含編譯后的機(jī)器碼,也保留了必要的字節(jié)碼,通過運(yùn)行時(shí)動(dòng)態(tài)選擇執(zhí)行路徑,平衡了安裝速度、存儲(chǔ)空間和運(yùn)行效率。

OAT 文件和類的加載

ART 虛擬機(jī)在加載 OAT 文件中的類時(shí),總體上遵循 JVM 的類加載階段(加載→驗(yàn)證→準(zhǔn)備→解析→初始化),其中驗(yàn)證,準(zhǔn)備,解析也可以認(rèn)為是連接過程。但由于 ART 的 AOT 編譯特性和 Android 運(yùn)行時(shí)優(yōu)化,具體實(shí)現(xiàn)細(xì)節(jié)與傳統(tǒng) JVM 有顯著差異。以下是逐階段的對(duì)比與分析:


1. 加載(Loading)

  • JVM
    • 通過 ClassLoader 查找 .class 文件,讀取二進(jìn)制字節(jié)流。
  • ART
    • 輸入源不同:直接從 OAT 文件加載(OAT 內(nèi)嵌了原始 DEX 字節(jié)碼或已編譯的機(jī)器碼)。
    • 內(nèi)存映射優(yōu)化:OAT 文件通過 mmap 映射到內(nèi)存,避免重復(fù) I/O 操作。
    • 共享機(jī)制:多個(gè)進(jìn)程共享同一 OAT 文件的只讀代碼段(通過 zygote 預(yù)加載)。

關(guān)鍵區(qū)別
ART 的加載階段更高效,直接利用預(yù)編譯的 OAT 文件,而非原始 DEX/Class。


2. 驗(yàn)證(Verification)

  • JVM
    • 在運(yùn)行時(shí)逐條驗(yàn)證字節(jié)碼的合法性(如類型檢查、控制流完整性)。
  • ART
    • AOT 提前驗(yàn)證:在安裝或編譯時(shí)(dex2oat 階段)完成大部分驗(yàn)證。
    • 運(yùn)行時(shí)輕量級(jí)驗(yàn)證:僅對(duì)未驗(yàn)證的部分(如動(dòng)態(tài)加載的 DEX)進(jìn)行補(bǔ)充驗(yàn)證。
    • 優(yōu)化措施:若 OAT 文件是系統(tǒng)信任的(如系統(tǒng)應(yīng)用),可能跳過驗(yàn)證。

關(guān)鍵區(qū)別
ART 將驗(yàn)證開銷從運(yùn)行時(shí)轉(zhuǎn)移到安裝/編譯時(shí),提升運(yùn)行時(shí)性能。


3. 準(zhǔn)備(Preparation)

  • JVM
    • 為類的靜態(tài)字段分配內(nèi)存并初始化為默認(rèn)值(如 int 初始化為 0)。
  • ART
    • 行為一致:同樣分配靜態(tài)字段內(nèi)存,但可能直接使用 AOT 編譯時(shí)預(yù)計(jì)算的結(jié)果。
    • 內(nèi)存布局優(yōu)化:靜態(tài)字段的偏移地址在 AOT 編譯時(shí)已確定,減少運(yùn)行時(shí)計(jì)算。

關(guān)鍵區(qū)別
ART 利用 AOT 信息優(yōu)化內(nèi)存布局,但邏輯與 JVM 相同。


4. 解析(Resolution)

  • JVM
    • 將符號(hào)引用(如類名、方法名)轉(zhuǎn)換為直接引用(內(nèi)存地址)。
  • ART
    • 部分提前解析:AOT 編譯時(shí)已解析部分符號(hào)(如系統(tǒng)類引用)。
    • 延遲解析(Lazy Resolution)
      • 非關(guān)鍵路徑的符號(hào)引用在首次訪問時(shí)解析。
      • 通過 “Trampoline”跳轉(zhuǎn)代碼 動(dòng)態(tài)綁定目標(biāo)方法。

關(guān)鍵區(qū)別
ART 通過混合解析策略(AOT + 延遲)減少啟動(dòng)開銷。


5. 初始化(Initialization)

  • JVM
    • 執(zhí)行類的 <clinit> 方法(靜態(tài)代碼塊初始化)。 類初始化階段 在 java.lang.Class 層面仍然有鎖保護(hù),保證一個(gè)類的 <clinit> 方法(靜態(tài)初始化塊)只會(huì)執(zhí)行一次。
  • ART
    • 行為一致:必須執(zhí)行 <clinit>,但 AOT 編譯的 <clinit> 以機(jī)器碼形式運(yùn)行更快。
    • 并發(fā)優(yōu)化:Android 8.0+ 支持多線程并發(fā)初始化類,避免死鎖。
    • ART 內(nèi)部對(duì) 類加載過程 進(jìn)行了管理,并保證 類的元信息不會(huì)被重復(fù)創(chuàng)建。

關(guān)鍵區(qū)別
邏輯與 JVM 一致,但執(zhí)行效率更高。


ART 特有的額外階段

由于 AOT 編譯和 Android 運(yùn)行時(shí)優(yōu)化,ART 在類加載過程中還涉及以下步驟:

1. 機(jī)器碼綁定(AOT Code Binding)

  • AOT 編譯的方法需綁定到當(dāng)前進(jìn)程的內(nèi)存地址(處理 ASLR 重定位)。

2. 編譯狀態(tài)管理

  • 每個(gè)方法標(biāo)記為 AOT/JIT/解釋執(zhí)行狀態(tài),運(yùn)行時(shí)動(dòng)態(tài)切換。

3. Profile-Guided 優(yōu)化(Android 7.0+)

  • 根據(jù)運(yùn)行時(shí)采集的性能分析數(shù)據(jù)(.prof 文件),后臺(tái)重新優(yōu)化 OAT 文件。

對(duì)比總結(jié)

階段 JVM (Class) ART (OAT)
加載 .class 文件讀取 從 OAT 內(nèi)存映射,嵌入 DEX/機(jī)器碼
驗(yàn)證 運(yùn)行時(shí)完整驗(yàn)證 大部分在 AOT 編譯時(shí)完成
準(zhǔn)備 分配靜態(tài)字段內(nèi)存 同 JVM,但利用 AOT 偏移優(yōu)化
解析 運(yùn)行時(shí)完全解析 部分 AOT 解析 + 延遲解析
初始化 執(zhí)行 <clinit> 同 JVM,AOT 機(jī)器碼更快
額外步驟 機(jī)器碼綁定、編譯狀態(tài)管理、PGO 優(yōu)化

為什么 ART 仍需遵循這些階段?

  1. Java 語言規(guī)范要求
    類加載的語義必須符合 JVM 規(guī)范(如靜態(tài)字段初始值、初始化順序)。
  2. 動(dòng)態(tài)特性支持
    反射、動(dòng)態(tài)代理等功能依賴完整的類加載流程。
  3. 安全性保障
    驗(yàn)證階段防止惡意字節(jié)碼破壞運(yùn)行時(shí)。

ART 在加載 OAT 文件時(shí) 保留了 JVM 類加載的核心階段,但通過 AOT 編譯和內(nèi)存映射等技術(shù)大幅優(yōu)化了各階段的性能。理解這些差異有助于針對(duì) Android 平臺(tái)優(yōu)化應(yīng)用啟動(dòng)速度與內(nèi)存占用。

類加載器

在 Android ART 虛擬機(jī)中,類加載器的設(shè)計(jì)與標(biāo)準(zhǔn) JVM 有所不同。以下是完整的類加載器體系及其關(guān)系的詳細(xì)說明,特別澄清 BootstrapClassLoader 的角色以及 Android 特有的實(shí)現(xiàn):


Android ART 中的類加載器完整列表

1. BootClassLoader(核心系統(tǒng)加載器)

  • 作用:加載 Android 框架層的核心類(如 android.*、java.*、javax.*),對(duì)應(yīng) libcoreframework 的 OAT 文件。
  • 特點(diǎn)
    • 由 ART 虛擬機(jī)內(nèi)部實(shí)現(xiàn)(Java 層不可直接訪問,無對(duì)應(yīng)的 Java 類)。
    • 是所有類加載器的父加載器(雙親委派模型的頂端)。
  • 路徑
    核心類預(yù)編譯為 /system/framework/oat/<arch>/boot.oat(如 boot-framework.oat)。

2. PathClassLoader(應(yīng)用主加載器)

  • 作用:加載已安裝 APK 的主 DEX 文件(classes.dex 及優(yōu)化后的 OAT 文件)。
  • 特點(diǎn)
    • 父加載器是 BootClassLoader。
    • 只能加載 /data/app/<package>/ 下的固定路徑文件。
  • 使用場(chǎng)景
    應(yīng)用默認(rèn)的類加載器,通過 Context.getClassLoader() 獲取。

3. DexClassLoader(動(dòng)態(tài)加載器)

  • 作用:加載外部存儲(chǔ)的 DEX/JAR/APK 文件(需指定路徑)。
  • 特點(diǎn)
    • 父加載器也是 BootClassLoader
    • Android 8.0+ 后受限(需適配 AppComponentFactory)。
  • 使用場(chǎng)景
    插件化、熱修復(fù)等動(dòng)態(tài)加載技術(shù)。

4. InMemoryDexClassLoader(Android 8.0+ 新增)

  • 作用:直接加載內(nèi)存中的 DEX 字節(jié)數(shù)組(byte[])。
  • 特點(diǎn)
    • 避免文件寫入權(quán)限問題,適合安全敏感場(chǎng)景。
    • 父加載器可自定義(通常為 PathClassLoader)。
  • 示例
    byte[] dexBytes = ...; // 從網(wǎng)絡(luò)或加密文件讀取的 DEX 數(shù)據(jù)
    InMemoryDexClassLoader loader = new InMemoryDexClassLoader(
        ByteBuffer.wrap(dexBytes),
        getClassLoader()
    );
    

5. DelegateLastClassLoader(Android 9.0+ 新增)

  • 作用反向雙親委派(先嘗試自己加載,失敗后再委托父加載器)。
  • 使用場(chǎng)景
    需要覆蓋系統(tǒng)類行為的特殊需求(如兼容性庫)。
  • 示例
    DelegateLastClassLoader loader = new DelegateLastClassLoader(
        "/path/to/dex",
        null, // 父加載器(null 表示 BootClassLoader)
        getClassLoader(),
        false // 是否優(yōu)先加載自身路徑
    );
    

與標(biāo)準(zhǔn) JVM 的對(duì)比

類加載器 JVM Android ART
BootstrapClassLoader 加載 rt.jar 等核心庫(C++實(shí)現(xiàn)) 不存在,由 BootClassLoader 替代
ExtensionClassLoader 加載 ext 目錄擴(kuò)展庫 不存在
AppClassLoader 加載用戶類路徑(-classpath PathClassLoader 替代
  • 關(guān)鍵區(qū)別
    Android 沒有 BootstrapClassLoaderExtensionClassLoader,其功能由 BootClassLoader 統(tǒng)一實(shí)現(xiàn)。

為什么 Android 沒有 BootstrapClassLoader?

  1. 設(shè)計(jì)簡(jiǎn)化
    Android 的核心類(如 java.lang.String)直接編譯在 boot.oat 中,由 BootClassLoader 加載,無需區(qū)分 BootstrapExtension
  2. 安全模型
    Android 通過沙箱和權(quán)限控制替代 JVM 的 SecurityManager,無需復(fù)雜的類加載分層。
  3. 性能優(yōu)化
    預(yù)編譯的 OAT 文件減少了運(yùn)行時(shí)解析開銷,合并加載器層級(jí)可加速類查找。

開發(fā)者注意事項(xiàng)

  1. 動(dòng)態(tài)加載的兼容性
    • Android 7.0+ 限制私有 API 訪問,動(dòng)態(tài)加載的類可能無法調(diào)用系統(tǒng)隱藏 API。
    • Android 11+ 要求 Scoped Storage,外部 DEX 文件需存儲(chǔ)到應(yīng)用專屬目錄。
  2. 調(diào)試類加載器
    // 打印類加載器層次
    ClassLoader cl = MyClass.class.getClassLoader();
    while (cl != null) {
        Log.d("ClassLoader", cl.toString());
        cl = cl.getParent();
    }
    
  3. 自定義類加載器
    • 可繼承 BaseDexClassLoader,但需注意 Android 版本差異(如 8.0 后的優(yōu)化目錄策略)。

多線程的情況下,類的加載為什么不會(huì)出現(xiàn)重復(fù)加載的情況?

總結(jié)

Android ART 的類加載器體系是 JVM 的簡(jiǎn)化版,核心包括:

  • BootClassLoader(系統(tǒng)級(jí))
  • PathClassLoader(應(yīng)用主路徑)
  • DexClassLoader(動(dòng)態(tài)加載)
  • InMemoryDexClassLoader(內(nèi)存加載)
  • DelegateLastClassLoader(反向委派)

不存在 BootstrapClassLoader,其功能由 BootClassLoader 替代。理解這些加載器的差異和限制,對(duì)插件化、熱修復(fù)等高級(jí)開發(fā)場(chǎng)景至關(guān)重要。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容