OAT文件內(nèi)容
在 Android 7.0 (Nougat) 及以后版本中,ART 的混合編譯模式改變了 OAT 文件的結(jié)構(gòu)。以下是關(guān)鍵點(diǎn)的詳細(xì)解釋:
-
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)系。
-
混合編譯邏輯:
- 安裝時(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 文件中
-
文件結(jié)構(gòu)示例:
OAT 文件 ├── 已編譯方法 (機(jī)器碼) ├── 未編譯方法 (DEX 字節(jié)碼片段) ├── 方法調(diào)用跳轉(zhuǎn)表 └── 元數(shù)據(jù) (記錄編譯狀態(tài)) -
版本演進(jìn):
- Android 7.0 引入混合模式,解決純 AOT 導(dǎo)致的安裝時(shí)間過長和存儲(chǔ)占用問題。
- Android 8.0 進(jìn)一步優(yōu)化 JIT 緩存持久化(通過
dex2oat后臺(tái)任務(wù))。
-
開發(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)證。
-
AOT 提前驗(yàn)證:在安裝或編譯時(shí)(
關(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)。
- 為類的靜態(tài)字段分配內(nèi)存并初始化為默認(rèn)值(如
-
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í)行一次。
- 執(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)建。
-
行為一致:必須執(zhí)行
關(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 仍需遵循這些階段?
-
Java 語言規(guī)范要求:
類加載的語義必須符合 JVM 規(guī)范(如靜態(tài)字段初始值、初始化順序)。 -
動(dòng)態(tài)特性支持:
反射、動(dòng)態(tài)代理等功能依賴完整的類加載流程。 -
安全性保障:
驗(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)libcore和framework的 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 沒有BootstrapClassLoader和ExtensionClassLoader,其功能由BootClassLoader統(tǒng)一實(shí)現(xiàn)。
為什么 Android 沒有 BootstrapClassLoader?
-
設(shè)計(jì)簡(jiǎn)化:
Android 的核心類(如java.lang.String)直接編譯在boot.oat中,由BootClassLoader加載,無需區(qū)分Bootstrap和Extension。 -
安全模型:
Android 通過沙箱和權(quán)限控制替代 JVM 的SecurityManager,無需復(fù)雜的類加載分層。 -
性能優(yōu)化:
預(yù)編譯的 OAT 文件減少了運(yùn)行時(shí)解析開銷,合并加載器層級(jí)可加速類查找。
開發(fā)者注意事項(xiàng)
-
動(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)用專屬目錄。
-
調(diào)試類加載器:
// 打印類加載器層次 ClassLoader cl = MyClass.class.getClassLoader(); while (cl != null) { Log.d("ClassLoader", cl.toString()); cl = cl.getParent(); } -
自定義類加載器:
- 可繼承
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)重要。