Android profile-guided dex2oat

KeyWord: 

ART,Dalivk,.class file,dex file, java bytecode, dalvik bytecode, oat file,profile,dex2oat,app-image


1. ART vs Dalvik 涉及的各類文件

 

開始之前,簡單介紹下ART和Dalvik。

我們知道 java是運行在java虛擬機JVM上,所以JAVA代碼可移植性比較強。

Android是可以運行java代碼的,所以其應該有一個Jvm虛擬機或者說實現了一個自己的Jvm虛擬機。而Dalvik就是最初的Android平臺上的Jvm實現,用以使得Android上能夠運行java代碼。

1.1 java .class文件

我們寫出的java代碼是 .java 文件,而運行在 Jvm上之前,我們會先對 java文件進行編譯。

比如: javac Hello.java ,會將 java代碼編譯成 java bytecode,放在生成的一個名稱爲Hello.class的文件,在Jvm運行Hello程序的時候,會裝載並解釋 Hello.class文件中的 java bytecode進行執行;

java文件對於 java bytecode的關係,可以大概類比 C文件和彙編指令的關係。每一個java方法都是由多條 java bytecode 組成的。

需要注意的是,java文件中的每個類都會被編譯成一個 .class文件。

比如,Test.java 內容如下:

class Test {
  public static void main(String[] args) {

  }

  class InnerClass {

  }//class Innerclass
}// class Test

運行 javac Test.java 命令,編譯完成後,會生成 “Test.class” 和 “Test$InnerClass.class” 兩個 .class文件;每個類對應一個。 

簡單來講: JVM 執行 .class文件,識別 .class 中的 java bytecode並執行

.class文件及java bytecode的分析:

按照上面的步驟, javac Hello.java 生成的 .class文件可以用來分析,可以使用 010editor工具 進行分析 .class文件的文件結構。

可以使用 javap 命令把 .class文件生成 java bytecode進行分析(具體使用方法 javap -help)。

java bytecode大約有幾十個,可以參考:https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

1.2 dex文件

dex 應該解釋爲: dalvik executable.

sdk有個工具dex,用來把 1.1節中講到的 .class文件“打包”成 dex文件,這裏用“打包”這個詞的意思是:

把所有 javac 編譯生成的 .class文件,打包成 classes.dex文件。

優化操作:

“打包”過程,存在優化操作,比如CassA和ClassB中都有一個字符串“Hello World!”,則ClassA和ClassB被打包到的同一個 classes.dex文件中,只存在一個字符串。這是打包過程中,其中的一個“優化”。

java bytecode轉換爲 dalvik byte code:

打包過程中,會把 .class文件中的所有 java bytecode 轉換爲 dalvik bytecode,因爲 dalvik只能識別 dalvik bytecode,並執行它們.

簡單來講: Dalvik 執行 .dex文件,識別 .dex中的 dalvik bytecode並執行

.dex文件及dalvik bytecode的分析:

解壓一個apk文件,獲取 classes.dex文件,同樣可以使用 010editor進行分析學習。

可以使用 dexdump 分析dex文件(具體使用方法: dexdump 不加任何參數,會打印出help信息)。

dalvik bytecode 可以參考: http://source.android.com/devices/tech/dalvik/dalvik-bytecode.html


JIT(Just In Time):

本節需要簡單瞭解下JIT。

我們知道,C/C++的效率要比 Java好,因爲C/C++會被直接編譯成彙編指令,CPU可以直接讀取運行;而Java卻是需要虛擬機一步一步的解釋每一條 java bytecode。

而Dalvik 中使用了一個技術,叫做JIT,會在解釋執行一個java方法或者一個java代碼段時,進行trace,並在不斷的執行過程中找到 hotspot,

然後將相應的方法或者代碼片段編譯爲對應的彙編指令,下次再執行到該方法時,會直接執行其對應的彙編指令,依次來提升部分效率。

可以理解爲:運行時追蹤,並對hotspot進行編譯生成高效的可執行指令。

1.3 oat文件

前面1.1和1.2兩節,簡單介紹了 JVM 和Dalvik VM,總得來講,兩句話: JVM執行 java 字節碼, Dalvik執行 dalvik 字節碼。

ART(Android Runtime),是Android4.4上開始提供的另一個 JVM實現,在4.4時,默認的虛擬機還是 dalvik,ART作爲可選項,到Android5.0,開始作爲Android默認的虛擬機。

同樣的,ART也支持運行 dalvik bytecode(否則沒有辦法兼容之前的app),另外 ART 提出了一個 AOT(Ahead of time)的方法。

這個 AOT就是相對於 1.2節中提到的 JIT, AOT是說在代碼運行之前進行編譯。即把dex文件中的 dalvik bytecode編譯爲處理器可識別執行的彙編指令,我們把編譯後生成的代碼稱爲Native code。

而OAT文件就是包含了dex文件,dex文件編譯出的 native Code,以及OAT header,OAT class等組織文件的數據。

在使用oat文件的時候,通過這些組織關係,來查找一個類中java函數對應的 native code,從而在執行時去運行 native code;

實際上app編譯出來的OAT文件是一種特殊的ELF文件,在這個ELF文件的 oatdata 和 oatlastword之間的數據爲oat數據。也即 oat文件數據時嵌入在ELF文件中的。

ART運行的時候,會查詢當前app對應的 oat文件進行執行,當找不到oat文件時再解釋dex的 bytecode 執行。

簡單來講:ART執行 oat文件,執行其中 java 函數對應 native code; 當函數沒有對應的native code或者app沒有對應的oat文件時,仍然解釋執行dex文件中其對應的 dalvik bytecode。

  

1.4 profile文件

profile文件:/data/misc/profiles/cur/0/com.***.home/primary.prof

每個app的profile文件都在 /data/misc/profiles/ 目錄下。profile文件用來記錄運行比較頻繁的代碼,用來進行 profile-guide 編譯,使得 dex2oat編譯代碼更精準。

profile的創建:

App安裝的過程中,會調用到 isntalld的 create_app_data()函數,

如果當前支持profile編譯,則會爲app創建 profile文件。

int create_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
        appid_t appid, const char* seinfo, int target_sdk_version) {
      ...
      if (property_get_bool("dalvik.vm.usejitprofiles")) {
            std::string profile_file = create_primary_profile(profile_path);//組織 profile文件所在路徑 
            if (fs_prepare_file_strict(profile_file.c_str(), 0600, uid, uid) != 0) {//在這裏創建 profile文件,且只對owner Read-Write
                return -1;
            }
       ...
      }
}

profile信息的收集:

在App啓動的時候,開啓profile的收集線程:

->ActivityThread.main()
->...
->ActivityThread.performLaunchActivity()
->ActivityClientRecord.packageInfo.getClassLoader()
->LoadedApk.getClassLoader()
->setupJitProfileSupport()
VMRuntime.registerAppInfo(profileName)
Runtime::RegisterAppInfo(profileName)
jit_-> StartProfileSaver(profileName)
ProfileSaver::Start(profilName)//在這裏會創建一個thread 用來收集 resolved class與method

ProfileSaver::Run() {
FetchAndCacheResolvedClassesAndMethods();
bool profile_saved_to_disk = ProcessProfilingInfo(&new_methods); // 在這個方法中會把達到條件的 methodId 和 classid記錄到 profile文件
}
void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() {
  std::set<DexCacheResolvedClasses> resolved_classes = class_linker->GetResolvedClasses(/*ignore boot classes*/ true);
  std::vector<MethodReference> methods;
  {
    ScopedTrace trace2("Get hot methods");
    GetMethodsVisitor visitor(&methods);
    ScopedObjectAccess soa(Thread::Current());
    class_linker->VisitClasses(&visitor);
  }
  for (const auto& it : tracked_dex_base_locations_) {
    for (const MethodReference& ref : methods) {
      if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) {
        methods_for_location.push_back(ref);
      }
    }
    for (const DexCacheResolvedClasses& classes : resolved_classes) {
      if (locations.find(classes.GetBaseLocation()) != locations.end()) {
        resolved_classes_for_location.insert(classes);
      }
    }
    ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
    info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location);
  }
}

在這個方法中,會編譯當前進程中所有已經Load的Class,如果這些class是apk中的class,則將會被添加到 profile信息中。

對於要記錄的 method則需要達到一定的條件(函數的調用次數):

函數調用次數有以下幾個 threshold:

  uint16_t hot_method_threshold_;

  uint16_t warm_method_threshold_;

  uint16_t osr_method_threshold_;

// Minimum number of JIT samples during launch to include a method into the profile.
static constexpr const size_t kStartupMethodSamples = 1;
  uint16_t GetCounter() const { // 獲取當前函數被interpreter調用過的次數
    return hotness_count_;
  }
  void SetCounter(int16_t hotness_count) {
    hotness_count_ = hotness_count;
  }
  // The hotness we measure for this method. Managed by the interpreter. Not atomic, as we allow
  // missing increments: if the method is hot, we will see it eventually.
  uint16_t hotness_count_;
class GetMethodsVisitor : public ClassVisitor {
  virtual bool operator()(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) {
    if (Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) { // 屬於 bootimage中的class,不對其method進行收集
      return true;
    }
    for (ArtMethod& method : klass->GetMethods(sizeof(void*))) { // 編譯class的所有 method
      if (!method.IsNative()) { //  jni method 不需要收集
        if (method.GetCounter() >= kStartupMethodSamples ||  // 當前method曾經被添加到 JIT Sample中
            method.GetProfilingInfo(sizeof(void*)) != nullptr) { // 爲當前method 創建過 ProfileInfo信息,當函數的調用次數,從小於warm_method_threshold_,增加到 大於等於 warm_method_threshold_時,會爲method創建profile信息
          // Have samples, add to profile.
          const DexFile* dex_file = method.GetInterfaceMethodIfProxy(sizeof(void*))->GetDexFile();
          methods_->push_back(MethodReference(dex_file, method.GetDexMethodIndex())); // 達到條件,把當前method 放要準備記錄到 profile中的 methods_ 集合中
        }
      }
    }
    return true;
  }
}

另外可以看到在這裏,只過濾了 Bootimage 中的class,而沒有過濾第三方的 class,比如 use-library 包含的class,也就是說,除了bootimage 中的 class 外,其他的 class 的 method(比如use-library中對應的class)只要調用次數達到限制,都會被記錄到 profile中;

 method的調用次數修改和Method profile信息添加如下都在如下函數中:

void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_backedges) {
  int32_t starting_count = method->GetCounter();
  int32_t new_count = starting_count + count; // 把method 調用次數增加 count
 if (starting_count < warm_method_threshold_) {
    if ((new_count >= warm_method_threshold_) &&
        (method->GetProfilingInfo(sizeof(void*)) == nullptr)) {
      bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false); // 在這裏會把新創建的 profileInfo設置到 method中
      if (success) {
        VLOG(jit) << "Start profiling " << PrettyMethod(method);
      }
      if (thread_pool_ == nullptr) {
        DCHECK(Runtime::Current()->IsShuttingDown(self));
        return;
      }
      if (!success) {
        thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile));// 如果上面一步失敗,則會在Task中調用 ProfilingInfo::Create(),把新創建的 profileInfo設置到 method中
      }
    }
    new_count = std::min(new_count, hot_method_threshold_ - 1);
  } else if (use_jit_compilation_) {
    if (starting_count < hot_method_threshold_) {
      if ((new_count >= hot_method_threshold_) &&
          !code_cache_->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) {
        DCHECK(thread_pool_ != nullptr);
        thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile));
      }
      new_count = std::min(new_count, osr_method_threshold_ - 1);
    } else if (starting_count < osr_method_threshold_) {
      if (!with_backedges) {
        return;
      }
      if ((new_count >= osr_method_threshold_) &&  !code_cache_->IsOsrCompiled(method)) {
        thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompileOsr));
      }
    }
  }
 
  // Update hotness counter
  method->SetCounter(new_count); // 把新的調用次數設置到該函數中
}
bool ProfilingInfo::Create(Thread* self, ArtMethod* method, bool retry_allocation) {
  ...
  jit::JitCodeCache* code_cache = Runtime::Current()->GetJit()->GetCodeCache();
  return code_cache->AddProfilingInfo(self, method, entries, retry_allocation) != nullptr;
}
 
ProfilingInfo* JitCodeCache::AddProfilingInfo(Thread* self,
                                              ArtMethod* method,
                                              const std::vector<uint32_t>& entries,
                                              bool retry_allocation) {
      info = AddProfilingInfoInternal(self, method, entries);
      ...
}
 
ProfilingInfo* JitCodeCache::AddProfilingInfoInternal(Thread* self ATTRIBUTE_UNUSED,
                                                      ArtMethod* method,
                                                      const std::vector<uint32_t>& entries) {
  ...
  info = new (data) ProfilingInfo(method, entries);
  method->SetProfilingInfo(info);
  profiling_infos_.push_back(info);
}

在解釋執行一個函數時,會調用 AddSamples函數,從而會記錄函數的調用次數。從而生成profile文件。生成的profile文件格式如下:

profile文件格式:

/**
 * Serialization format:
 *    magic,version,number_of_lines
 *    dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \
 *        method_id11,method_id12...,class_id1,class_id2...
 *    dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \
 *        method_id21,method_id22...,,class_id1,class_id2...
 *    .....
 **/

profile文件 的查看:

xxxx:/data/misc/profiles/cur/0/com.***.home # profman --profile-file=primary.prof --dump-only                                                                                                           
=== profile ===
ProfileInfo:
XXXHome.apk
    methods: 1824,1837,1843,1846,1907,1908,1912,1916,1933,1961,1971,1980,1981,1986,2009,2041,2043,2045,2049,2066,2070,2089,2091,2093,2095,2103,2105,2107,2109,2114,2115,2149,2150,2151,2167,2197,2214,2216,2221,2233,2234,2239,2277,2278,2287,2290,2293,2301,2302,2316,2330,2342,2348,2356,2358,2377,2378,2379,2383,2391,2396,2400,2405,2407,2409,2429,2430,2458,2471,2484,2487,2490,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2510,2511,2512,2514,2517,2527,2528,2529,2533,2534,2535,2536,2537,2538,2542,2543,2544,2545,2548,2550,2551,2553,2554,2555,2556,2560,2562,2564,2567,2574,2578,2581,2596,2616,2632,2656,2683,2684,2692,2693,2696,2797,2801,2829,2878,2879,2884,2885,2896,2898,2901,2911,2934,2939,2943,2945,2947,2950,2989,3021,3058,3060,3062,3064,3079,3116,3153,3154,3157,3160,3162,3174,3286,3321,3381,3412,3416,3418,3430,3431,3434,3443,3444,3451,3453,3461,3462,3482,3491,3542,3582,3588,3643,3647,3650,3676,3681,3683,3687,3688,3690,3696,3704,3705,3713,3714,3715,3727,3731,3738,3749,3784,3796,3824,3838,3842,3844,3846,3848,3856,3860,3862,3863,3864,3866,3869,3870,3936,3957,3959,3972,3992,4022,4028,4034,4045,4052,4074,4117,4151,4157,4158,4159,4162,4163,4172,4174,4190,4195,4230,4237,4242,4287,4290,4291,4292,4297,4300,4305,4306,4352,4353,4367,4368,4424,4453,4465,4466,4474,4478,4479,4480,4482,4488,4497,4499,4501,4510,4532,4537,4538,4540,4560,4563,4573,4579,4580,4583,4588,4593,4596,4605,4608,4611,4630,4631,4632,4634,4650,4722,4742,4745,4765,4769,4774,4786,4794,4811,4813,4814,4816,4821,4822,4859,4860,4875,4891,4921,4924,4925,4933,4947,4952,4977,4986,4987,4988,4989,5014,5015,5019,5020,5022,5024,5040,5060,5061,5065,5072,5073,5074,5075,5081,5087,5097,5098,5106,5108,5124,5127,5129,5141,5142,5143,5152,5153,5161,5178,5180,5185,5186,5191,5192,5195,5196,5198,5199,5201,5202,5203,5204,5206,5208,5224,5225,5226,5229,5251,5252,5255,5281,5290,5310,5322,5325,5330,5331,5342,5351,5371,5375,5380,5382,5387,5391,5432,5433,5438,5461,5462,5465,5466,5469,5471,5486,5488,5505,5506,5514,5530,5532,5536,5537,5560,5561,5574,5592,5618,5619,5638,5663,5665,5700,5715,5721,5752,5771,5779,5784,5788,5790,5796,5799,5808,5809,5814,5815,5817,5825,5830,5839,5842,5850,5853,5855,5857,5858,5869,5888,5890,5902,5911,5929,5931,5933,5937,5964,5965,5971,5983,5986,5987,5988,5989,6039,6041,6066,6070,6089,6159,6162,6163,6165,6167,6169,6171,6183,6184,6240,6245,6248,6254,6255,6256,6277,6278,6282,6283,6285,6287,6288,6289,6291,6297,6298,6300,6302,6304,6311,6312,6319,6364,6379,6384,6402,6404,6406,6453,6517,6531,6533,6534,6536,6542,6548,6551,6743,6776,6778,6779,6780,6781,6783,6785,6786,6788,6796,6948,6951,6953,6955,6957,6960,6964,6967,6971,6974,6975,6978,6979,6980,6981,7368,7370,7374,7375,7558,7563,7612,7613,7614,7637,7638,7640,7641,7642,7643,7644,7648,7649,8235,8895,8896,8897,8900,8901,8902,8905,8910,8916,8919,8924,8972,8973,8974,8976,
    classes: 62,63,64,68,69,74,75,77,79,83,86,88,89,90,91,92,93,94,95,96,97,101,102,103,104,107,108,109,110,111,112,113,114,115,120,121,124,125,126,128,129,130,131,132,136,137,138,139,142,143,145,146,147,155,157,158,165,166,167,174,175,176,177,178,179,183,184,185,186,187,188,193,194,195,196,197,201,202,203,208,229,231,232,233,235,236,237,238,240,243,257,272,275,287,294,295,296,297,298,301,303,304,305,306,307,308,309,310,314,315,336,337,357,359,361,364,365,366,367,369,371,372,373,375,376,378,379,380,382,386,387,388,392,395,397,398,401,402,403,404,405,416,422,423,426,427,428,430,433,434,440,444,450,451,452,457,458,459,460,467,468,469,470,471,472,473,474,481,482,483,484,485,486,487,488,491,492,501,502,503,504,513,521,522,543,544,545,558,559,619,642,650,654,754,755,

其中:

XXXHome.apk表示 dex文件的位置,如果這個apk中有多個dex,比如 classes.dex 和 classes2.dex,則classes2.dex中的類,則以 XXXHome.apk:classes2.dex 命名。

methods 和 classes 後面的數據,表示他們在dex文件中的index。

我們使用profile模式 dex2oat編譯時,會只編譯profile中記錄的這些 class 和 methods。

1.5 App-image 文件

/data/app/com.facebook.katana-1/oat/arm/base.art

/data/app/com.facebook.katana-1/oat/arm/base.odex

base.art就是對應的 app-image文件。

xxx:/data/dalvik-cache/arm # oatdump --app-image=system@priv-app@[email protected]@classes.art --app-oat=system@priv-app@[email protected]@classes.dex --image=/system/framework/boot.art --instruction-set=arm --header-only

獲取完整數據可以把 header-only 參數去掉即可。

base.art文件主要記錄已經編譯好的類的具體信息以及函數在oat文件的位置,相當於緩存,在app運行的時候會加載到虛擬機,可以加快啓動速度。

 

文件格式總結:

      1. java .class 文件,JVM執行的文件,Android平臺上,只在編譯過程中出現,不會直接使用在虛擬機中
      2. dex 文件,dalvik/ART執行的文件,由 java .class文件 “打包”而來
        1. odex 文件,odex 文件沒有在這裏介紹,這個是dalvik dexopt的產物,只有 Android4.4及以前的設備使用
      3. oat 文件,ART執行的文件,Android5.0 之後開始出現,裏面包含 dex 文件,native code等信息,由 dex文件經過 dex2oat 編譯而生成
      4. profile文件,Android7.0之後,ART使用的文件,用來進行 profile-guide編譯,即指導 dex2oat 如何編譯 dex文件
      5. app-image 文件,Android7.0之後,ART使用的文件,它是App使用的類以及函數數據的緩存,在app啓動的使用mmap到內存空間,以加快app啓動速度,App啓動過程越複雜,使用app-image時的提升越明顯

2. dex2oat 是什麼

dex2oat是一個可執行程序,在手機的 /system/bin/dex2oat,它的作用是編譯dex文件,生成oat文件。

 

在1.3節中,dex文件被編譯爲 oat文件的過程,就是由 /system/bin/dex2oat 程序觸發的; 而實際上編譯業務是在 libart-compiler.so中做的。

dex2oat(dex文件) => oat文件

其輸入是dex文件,包括包含dex文件的 zip文件/apk文件/jar文件,輸出就是 oat文件。

在Android7.0上,dex2oat的參數 compiler-filter 被指定爲 profile類型的幾個compiler-filter之一時,dex2oat還會生成 app-image文件。

所以 dex2oat(dex文件) => oat文件/image文件

 

在編譯的過程:

依次編譯輸入參數中的所有dex文件;  每個dex文件又按照單個class進行編譯; 對於每個class,依次編譯其除 abstract 函數之外的所有函數,包括 native(jni)/static/及一般函數,進行生成native code,並存放在compiler中。

當編譯完成後,會從compiler中把native code, dex文件,以及必要的組織信息,寫入到OAT文件中;如果指定了生成app-image,還會再生成一份 app-image文件。

我們知道 dex文件中的函數,是 dalvik bytecode ,所以 dex2oat編譯函數的過程,就是把 dalvik bytecode 轉換成 native code的過程。


3. dex2oat 什麼時候被觸發

dex2oat進程的啓動,可以分爲兩大類:一類是 installd進程觸發的dex2oat;另一類是由 app中直接調用的 dex2oat。

installd 中觸發的 dex2oat,有以下幾個場景:

1.應用安裝,(包括普通安裝和通過shellCmd安裝),安裝一個app時,安裝過程中需要編譯dex文件,會通知installd來觸發一個dex2oat進程;

2.開機掃描,開機過程中,PMS掃描已安裝app過程,判斷需要優化時,則會對install發出通知;

3.BackgroundDexOptService,(空閒時段或者開機之後觸發的Backgroud的 Job),會通知installd進行dex2oat;

4.OTADexoptService,好象是OAT過程中的觸發的,這個場景沒有進行過實際的驗證;

 

app中調用 dex2oat:

一般是App的進程fork出一個子進程,子進程用來執行dex2oat,編譯相關的dex,而父進程進行 waitpid 等待,等待完成後再運行其他邏輯。

比如:

1.微信安裝後的首次啓動,是有dex2oat的調用

2.淘寶安裝後的首次搜索,也有dex2oat的調用

這個也是其首次啓動或者搜索的一個耗時點。

 

舉個 BackgroudDexOptService觸發 profile類型的dex2oat的例子。

BackgroudDexOptService.java文件中,

private boolean runIdleOptimization(){
...
pm.performDexOpt(pkg, /* checkProfiles */ true, PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)
...
}
performDexOptTraced(packageName, checkProfiles, getCompilerFilterForReason(compileReason), force);

compileReason有以下幾種類型:

xx@1:~/source/7.0/xxx-n-dev$ adb shell getprop | grep dexopt
[pm.dexopt.ab-ota]: [speed-profile]
[pm.dexopt.bg-dexopt]: [speed-profile]
[pm.dexopt.boot]: [interpret-only]
[pm.dexopt.core-app]: [speed]
[pm.dexopt.first-boot]: [interpret-only]
[pm.dexopt.forced-dexopt]: [speed]
[pm.dexopt.install]: [interpret-only]
[pm.dexopt.nsys-library]: [speed]
[pm.dexopt.shared-apk]: [speed]

當BackgroudDexOptService 啓動空閒優化時,REASON_BACKGROUND_DEXOPT 對應的 compiler-filter 是 [speed-profile]。 

其參數 dexopt_flags:

/* all known values for dexopt flags */
     * **************************************************************************/
    /** Application should be visible to everyone */
    public static final int DEXOPT_PUBLIC         = 1 << 1;
    /** Application wants to run in VM safe mode */
    public static final int DEXOPT_SAFEMODE       = 1 << 2;//
    /** Application wants to allow debugging of its code */
    public static final int DEXOPT_DEBUGGABLE     = 1 << 3;
    /** The system boot has finished */
    public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
    /** Hint that the dexopt type is profile-guided. */
    public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
    /** This is an OTA update dexopt */
    public static final int DEXOPT_OTA            = 1 << 6;

performDexOptTraced 會調用到 PackageDexOptimizer.java中 如下方法:

 private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
            String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter, int extraFlags) {

參數 sharedLibraries 表示 app使用的 use-library: application標籤下的 <uses-library android:name="cloud-common.jar" /> 

另外會在這個方法中構建 dexopt_flags,最終傳遞給installd:

boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
final int dexFlags = adjustDexoptFlags(
                        ( isPublic ? DEXOPT_PUBLIC : 0)
                        | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                        | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                        | profileFlag
                        | DEXOPT_BOOTCOMPLETE);

只有如下返回true的幾種類型作爲 profileGuidedFilter:

switch (filter) {
    case CompilerFilter::kVerifyNone:
    case CompilerFilter::kVerifyAtRuntime:
    case CompilerFilter::kInterpretOnly:
    case CompilerFilter::kSpace:
    case CompilerFilter::kBalanced:
    case CompilerFilter::kTime:
    case CompilerFilter::kSpeed:
    case CompilerFilter::kEverything: return false;
 
    case CompilerFilter::kVerifyProfile:
    case CompilerFilter::kSpaceProfile:
    case CompilerFilter::kSpeedProfile:
    case CompilerFilter::kEverythingProfile: return true;
  }

其最終調用到通知 installd的 dexopt_delegate() 方法進行dex2oat;

int dexopt_delegate(const char* apk_path, uid_t uid, const char* pkgname, const char* instruction_set,
           int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
           const char* volume_uuid ATTRIBUTE_UNUSED, const char* shared_libraries, int extra_flags)

在installd中會把 dexopt_flags 拆解出來進行使用。

    bool is_public = ((dexopt_flags & DEXOPT_PUBLIC) != 0);
    bool vm_safe_mode = (dexopt_flags & DEXOPT_SAFEMODE) != 0;
    bool debuggable = (dexopt_flags & DEXOPT_DEBUGGABLE) != 0;
    bool boot_complete = (dexopt_flags & DEXOPT_BOOTCOMPLETE) != 0;
    bool profile_guided = (dexopt_flags & DEXOPT_PROFILE_GUIDED) != 0;

當進行profile類型的 dex2oat時,會同時給app生成 app-image

      add_extension_to_file_name(image_path, ".art")
      if (profile_guided && have_app_image_format) {
          image_fd = open_output_file(image_path, /*recreate*/true, /*permissions*/0600);
          if (image_fd < 0) {
              ALOGE("installd could not create '%s' for image file during dexopt\n", image_path);
          } else if (!set_permissions_and_ownership(image_fd, is_public, uid, image_path)) {
              image_fd = -1;
          }
      }

之後會 fork一個新的進程,進行run_dex2oat, 組織dex2oat的參數並調用 /system/bin/dex2oat程序執行。

在run_dex2oat()方法中,有可能還會重新修改 compiler-filter:

    if (skip_compilation) {
        strcpy(dex2oat_compiler_filter_arg, "--compiler-filter=verify-none");
        have_dex2oat_compiler_filter_flag = true;
        have_dex2oat_relocation_skip_flag = true;
    } else if (vm_safe_mode) {
        strcpy(dex2oat_compiler_filter_arg, "--compiler-filter=interpret-only");
        have_dex2oat_compiler_filter_flag = true;
    } else if (compiler_filter != nullptr &&
            strlen(compiler_filter) + strlen("--compiler-filter=") <
                    arraysize(dex2oat_compiler_filter_arg)) {
        sprintf(dex2oat_compiler_filter_arg, "--compiler-filter=%s", compiler_filter);
        have_dex2oat_compiler_filter_flag = true;
    }

且 shared_library會放在 classpath 中:

    if (shared_libraries != nullptr) {
        argv[i++] = RUNTIME_ARG;
        argv[i++] = "-classpath";
        argv[i++] = RUNTIME_ARG;
        argv[i++] = shared_libraries;
    }

在 run_dex2oat() 方法中組織完成 dex2oat參數後,進行執行dex2oat: execv(DEX2OAT_BIN, (char * const *)argv);

最終執行的命令類似如下:

dex2oat --dex-file=/data/app/com.facebook.katana-1/base.apk --app-image-file=/data/app/com.facebook.katana-1/oat/arm/base.art --oat-file=/data/app/com.facebook.katana-1/oat/arm/base.odex --instruction-set=arm --instruction-set-variant=kryo --instruction-set-features=default --runtime-arg -Xms64m --runtime-arg -Xmx512m --compiler-filter=interpret-only --image-format=lz4 --runtime-arg -classpath --runtime-arg /system/framework/com.google.android.maps.jar

這個是手動模擬的,文件的參數都是路徑,真正運行的時候, 文件一般是打開後,傳遞 fd 給 dex2oat。

這個命令表示,要進行dex2oat,且創建  app對應的 app-image (--app-image-file),編譯時使用 interpret-only的模式,該應用使用 library :com.google.android.maps.jar

從前面,我們知道只有profile-guided compiler-filter時纔會創建 app-image,而當前這個命令是 interpret-only 與 app-image的組合,所以,可能在installd中,先創建了app-image之後,又修改了compiler-filter的類型。

總結:

      1. 由系統觸發的dex2oat,都是通過通知installd來進行編譯業務
      2. 由應用觸發的dex2oat,一般都是自行構建參數,直接調用dex2oat

4. dex2oat生成 oat文件和app-image文件

dex2oat --dex-file=/data/app/com.facebook.katana-1/base.apk --app-image-file=/data/app/com.facebook.katana-1/oat/arm/base.art --oat-file=/data/app/com.facebook.katana-1/oat/arm/base.odex --instruction-set=arm --instruction-set-variant=kryo --instruction-set-features=default --runtime-arg -Xms64m --runtime-arg -Xmx512m --compiler-filter=interpret-only --image-format=lz4 --runtime-arg -classpath --runtime-arg /system/framework/com.google.android.maps.jar

4.1 dex2oat 主要流程

dex2oat main函數:

int main(int argc, char** argv) {
  int result = art::dex2oat(argc, argv);
...
}
static int dex2oat(int argc, char** argv) {
...
 dex2oat->ParseArgs(argc, argv);
  if (dex2oat->UseProfileGuidedCompilation()) {
    if (!dex2oat->LoadProfile()) {
      return EXIT_FAILURE;
    }
  }
  dex2oat->Setup();
  bool result;
  if (dex2oat->IsImage()) {
    result = CompileImage(*dex2oat);
  } else {
    result = CompileApp(*dex2oat);
  }
   ...
}

當使用profile-guide 編譯app時,會先 LoadProfile(),這裏就是 load /data/misc/profiles/cur/0/packagename/primary.prof,進行解析出 class index 和 method index,放到 ProfileCompilationinfo 中;

如果當前的編譯要生成 image時,走CompileImage流程,否則走CompileApp流程;

  bool IsImage() const {
    return IsAppImage() || IsBootImage();
  }

不論是編譯boot image(boot.art)或者時 app 要生成 image時都走該流程;

static int CompileApp(Dex2Oat& dex2oat) {
  dex2oat.Compile();
  if (!dex2oat.WriteOatFiles()) {
    dex2oat.EraseOatFiles();
    return EXIT_FAILURE;
  }
  ...
  if (!dex2oat.FlushCloseOatFiles()) {
    return EXIT_FAILURE;
  }
  dex2oat.DumpTiming();
  return EXIT_SUCCESS;
}
static int CompileImage(Dex2Oat& dex2oat) {
  dex2oat.LoadClassProfileDescriptors();
  dex2oat.Compile();
 
  if (!dex2oat.WriteOatFiles()) {
    dex2oat.EraseOatFiles();
    return EXIT_FAILURE;
  }
  ...
  // Creates the boot.art and patches the oat files.
  if (!dex2oat.HandleImage()) {
    return EXIT_FAILURE;
  }
  ...
  dex2oat.DumpTiming();
  return EXIT_SUCCESS;
}

CompileApp和CompileImage的區別是:

編譯image時需要 LoadClassProfileDescriptors() 產生 image_classes_ 集合,和生成 image(HandleImage());

在生成的app image中將會包含 image_classes_ 集合中類的對象,不在 image_classes_集合中的app的類的對象,將不會被生成到 app-image中。

LoadClassProfileDescriptors()在從 profile信息中獲取 image_classes_集合時,將會把 app dex 中的類以外的類,都過濾掉,比如 classpath dex 對應的類將不會生成到 app-image;

 void LoadClassProfileDescriptors() {
    if (profile_compilation_info_ != nullptr && app_image_) {
      std::set<DexCacheResolvedClasses> resolved_classes(profile_compilation_info_->GetResolvedClasses()); // 獲取 profile信息中記錄的所有 class
      // Filter out class path classes since we don't want to include these in the image.
      std::unordered_set<std::string> dex_files_locations;
      for (const DexFile* dex_file : dex_files_) {
        dex_files_locations.insert(dex_file->GetLocation());  // 當前app的所有dex file
      }
      for (auto it = resolved_classes.begin(); it != resolved_classes.end(); ) {
        if (dex_files_locations.find(it->GetDexLocation()) == dex_files_locations.end()) {  // 如果這個類不在當前app 的dex file中,則過濾掉
          VLOG(compiler) << "Removed profile samples for non-app dex file " << it->GetDexLocation();
          it = resolved_classes.erase(it);
        } else {
          ++it;
        }
      }
      image_classes_.reset(new std::unordered_set<std::string>(runtime->GetClassLinker()->GetClassDescriptorsForProfileKeys(resolved_classes)));
    }
  }

dex2oat流程總結:

    1. 根據dex2oat接收到的參數,組織編譯參數
    2. 如果是 profile-guide 編譯,則先進行 load app對應的 profile
    3. 收集參數中包含的所有dex file,啓動 Compiler 編譯這些dex file(classpath中對應的dex file,即uses-library 引用的jar文件,不會被編譯),編譯生成的數據放在compiler-driver中
    4. 使用 compiler-driver 中的數據,依據 oat文件設計的格式,組織成oat文件,嵌入到 ELF文件中
    5. 如果指定需要生成 app-image,則使用 HandleImage(), 生成app-image, 即 ***.art 文件

4.2 Compile 流程:

 // Create and invoke the compiler driver. This will compile all the dex files.
  void Compile() {
  ...
    driver_.reset(new CompilerDriver(compiler_options_.get(),
                                     verification_results_.get(),
                                     &method_inliner_map_,
                                     compiler_kind_,
                                     instruction_set_,
                                     instruction_set_features_.get(),
                                     IsBootImage(),
                                     IsAppImage(),
                                     image_classes_.release(),
                                     compiled_classes_.release(),
                                     /* compiled_methods */ nullptr,
                                     thread_count_,
                                     dump_stats_,
                                     dump_passes_,
                                     compiler_phases_timings_.get(),
                                     swap_fd_,
                                     profile_compilation_info_.get()));
    driver_->SetDexFilesForOatFile(dex_files_);
    driver_->CompileAll(class_loader_, dex_files_, timings_);
  }

編譯dex文件時在 CompilerDriver 中完成, 其中LoadProfile時構造的 profile_compilation_info_也會指導 將要編譯哪些class和 methods。

driver_->SetDexFilesForOatFile(dex_files_);//表示將要編譯的所有 dex file,這個集合是 --dex-file=/data/app/com.facebook.katana-1/base.apk 這個文件中包含的所有dex文件,比如facebook的apk中有 12個 dex文件,則會依次編譯這12個文件。

void CompilerDriver::CompileAll(jobject class_loader,
                                const std::vector<const DexFile*>& dex_files,
                                TimingLogger* timings) {
  InitializeThreadPools();
  // Precompile:
  // 1) Load image classes
  // 2) Resolve all classes
  // 3) Attempt to verify all classes
  // 4) Attempt to initialize image classes, and trivially initialized classes
  PreCompile(class_loader, dex_files, timings);
  // Compile:
  // 1) Compile all classes and methods enabled for compilation.
  if (!GetCompilerOptions().VerifyAtRuntime()) {
    Compile(class_loader, dex_files, timings);
  }
}
 
void CompilerDriver::PreCompile(jobject class_loader,
                                const std::vector<const DexFile*>& dex_files,
                                TimingLogger* timings) {
   LoadImageClasses(timings); //這裏只針對 bootimage的編譯
   Resolve(class_loader, dex_files, timings);
  Verify(class_loader, dex_files, timings);
  InitializeClasses(class_loader, dex_files, timings);
}
 
void CompilerDriver::Verify(jobject class_loader,
                            const std::vector<const DexFile*>& dex_files,
                            TimingLogger* timings) {
  for (const DexFile* dex_file : dex_files) {
    CHECK(dex_file != nullptr);
    VerifyDexFile(class_loader,
                  *dex_file,
                  dex_files,
                  parallel_thread_pool_.get(),
                  parallel_thread_count_,
                  timings);
  }
}
 
void CompilerDriver::VerifyDexFile(...){
  ...
  VerifyClassVisitor visitor(&context, log_level);
  context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count);
}
 
class VerifyClassVisitor : public CompilationVisitor {
 public:
  VerifyClassVisitor(const ParallelCompilationManager* manager, LogSeverity log_level)
     : manager_(manager), log_level_(log_level) {}
  virtual void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE {
    if (!manager_->GetCompiler()->ShouldVerifyClassBasedOnProfile(dex_file, class_def_index)) {
      // Skip verification since the class is not in the profile.
      return;
    }
    ...
  }
}
 
bool CompilerDriver::ShouldVerifyClassBasedOnProfile(const DexFile& dex_file,
                                                     uint16_t class_idx) const {
  ...
  bool result = profile_compilation_info_->ContainsClass(dex_file, class_idx);
  return result;
}

在這裏可以看到,前面從 profile中load出來的信息,將會決定只有這些 class纔會進行Verify


接下來看下真正的編譯,實際上編譯對應的是 dalvik bytecode到 native code的轉換,主要針對的 method;

void CompilerDriver::Compile(jobject class_loader,
                             const std::vector<const DexFile*>& dex_files,
                             TimingLogger* timings) {
  for (const DexFile* dex_file : dex_files) {
    CHECK(dex_file != nullptr);
    CompileDexFile(class_loader, *dex_file, dex_files, parallel_thread_pool_.get(), parallel_thread_count_, timings); // 按照dexfile 依次編譯
  }
  ...
}
void CompilerDriver::CompileDexFile(jobject class_loader,
                                    const DexFile& dex_file,
                                    const std::vector<const DexFile*>& dex_files,
                                    ThreadPool* thread_pool,
                                    size_t thread_count,
                                    TimingLogger* timings) {
  TimingLogger::ScopedTiming t("Compile Dex File", timings);
  ParallelCompilationManager context(Runtime::Current()->GetClassLinker(), class_loader, this,
                                     &dex_file, dex_files, thread_pool);
  CompileClassVisitor visitor(&context);
  context.ForAll(0, dex_file.NumClassDefs(), &visitor, thread_count); //從dexfile的第一個class,直到最後一個class
}

編譯的工作在 CompileClassVisitor 的Visit方法中進行;

class CompileClassVisitor : public CompilationVisitor {
 public:
  explicit CompileClassVisitor(const ParallelCompilationManager* manager) : manager_(manager) {}
  virtual void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE { // 傳遞的參數爲 class在 dexfile中的 index,以此來查找class 數據
    const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
    const char* descriptor = dex_file.GetClassDescriptor(class_def);
    Handle<mirror::Class> klass(hs.NewHandle(class_linker->FindClass(soa.Self(), descriptor, class_loader)));
    const uint8_t* class_data = dex_file.GetClassData(class_def);
    ClassDataItemIterator it(dex_file, class_data);
 
    while (it.HasNextDirectMethod()) { // 編譯direct mothod
      uint32_t method_idx = it.GetMemberIndex();
      CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
                    it.GetMethodInvokeType(class_def), class_def_index,
                    method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
                    compilation_enabled, dex_cache);
      it.Next();
    }
    while (it.HasNextVirtualMethod()) { // 編譯virtual methods
      uint32_t method_idx = it.GetMemberIndex();
      CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
                    it.GetMethodInvokeType(class_def), class_def_index,
                    method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
                    compilation_enabled, dex_cache);
      it.Next();
    }
    ...
  }
}

從這一步中,我們可以看到,編譯代碼工作,主要的就是編譯 method成爲 native code;

static void CompileMethod(Thread* self,
                          CompilerDriver* driver,
                          const DexFile::CodeItem* code_item,
                          uint32_t access_flags,
                          InvokeType invoke_type,
                          uint16_t class_def_idx,
                          uint32_t method_idx,
                          jobject class_loader,
                          const DexFile& dex_file,
                          optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level,
                          bool compilation_enabled,
                          Handle<mirror::DexCache> dex_cache)
    REQUIRES(!driver->compiled_methods_lock_) {
    MethodReference method_ref(&dex_file, method_idx);
    if ((access_flags & kAccNative) != 0) { // 編譯 JNI 函數
      compiled_method = driver->GetCompiler()->JniCompile(access_flags, method_idx, dex_file);
    } else if((access_flags & kAccAbstract) != 0) { // abstract 函數沒有代碼,不需要編譯
    } else { //編譯其他函數
    const VerifiedMethod* verified_method =
        driver->GetVerificationResults()->GetVerifiedMethod(method_ref);
    bool compile = compilation_enabled &&
        driver->GetVerificationResults()
            ->IsCandidateForCompilation(method_ref, access_flags) &&
        verified_method != nullptr &&
        !verified_method->HasRuntimeThrow() &&
        (verified_method->GetEncounteredVerificationFailures() &
            (verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&
        driver->IsMethodToCompile(method_ref) &&
        driver->ShouldCompileBasedOnProfile(method_ref);// 如果是profile-guide編譯,需要檢查是否是 profile中指定的函數,如果不是,則不編譯該函數
    if (compile) {
      // NOTE: if compiler declines to compile this method, it will return null.
      compiled_method = driver->GetCompiler()->Compile(code_item, access_flags, invoke_type,
                                                       class_def_idx, method_idx, class_loader,
                                                       dex_file, dex_cache);
    }
    ...
    driver->AddCompiledMethod(method_ref, compiled_method, non_relative_linker_patch_count);//把編譯得到的 compiled-method 添加到 compiler-driver中,以便後面生成oat文件時使用
    }
}

compiled-method 的生成過程,是真正ART編譯器工作的過程,使用了圖等算法進行編譯,非常複雜,這裏不再詳述,

總之,這個過程中,完成了dalvik bytecode 到 native code的轉化以及一定的優化,到這一步,我們得到了產出: compiled-method

ART運行過程中,執行函數時,如果這個函數被編譯過,那麼就會執行其對應的 compiled-method,否則繼續解釋執行其對應的 dalvik bytecode。

bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_ref) const {
  if (profile_compilation_info_ == nullptr) {
    // If we miss profile information it means that we don't do a profile guided compilation.
    // Return true, and let the other filters decide if the method should be compiled.
    return true;
  }
  bool result = profile_compilation_info_->ContainsMethod(method_ref);// 判斷當前method是不是在前面 load到的 profile 中
  return result;
} 

Compile流程總結:

    1. PreCompile 做一些準備工作,ResolveClass(可以認爲是從dex文件中構造class到內存中),VerifyClass(驗證錯誤),InitializeClass(初始化)等動作,做一些過濾動作,比如把verify失敗的class過濾掉
    2. Compile過程,多線成編譯,線程數目是 CPU count -1, 最小編譯單位是 method,依次按照method所在 dex,所在class進行編譯
    3. 如果存在profile的情況下,Verify過程只對profile中存在的Class進行verify,CompileMethod過程,只對profile中存在的method進行編譯
    4. 編譯後生成的compiled-method 放到 compiler-driver中,以備在dex2oat中,準備寫入OAT文件時使用

4.3 OAT 文件寫入流程


在Compile流程結束後,會進行OAT文件的寫入操作。

  enum class WriteState {
    kAddingDexFileSources, // 添加dex文件到 oat文件中
    kPrepareLayout, //準備文件佈局
    kWriteRoData, //寫入RoData
    kWriteText, //寫入代碼段
    kWriteHeader, // 寫入 oat header
    kDone // 寫入完成
  };

從OatWriteState可以看到,其寫入oat文件的流程。

      1. AddDexFileSource,在dex2oat Setup時,就已經將將要編譯的dex file 寫入到 OatWriter 中,並設置 write_state_ = WriteState::kPrepareLayout;
      2. 後續的步驟都在編譯完成後,由 WriteOatFiles 完成
      3. kPrepareLayout,初始化 OatClass,OatMaps,OatCode, 準備OatMethod信息 和 bss段的DexCacheArray
      4. kWriteRoData,寫入 readOnly 數據,依次寫入 ClassOffset,寫入 OatClass,寫入函數的vmap Table,寫入 padding
      5. kWriteText,對於要生成 bootimage時,寫入trampoline,對與app只寫入quick code
      6. kWriteHeader,填充 Oat Header信息,寫入到oat文件

 bool Setup() {
    CreateOatWriters();
    if (!AddDexFileSources()) {
      return false;
    }
 }
 
  bool WriteOatFiles() {
    if (IsImage()) { // 如果本次dex2oat要生成 image,則會在寫入 oat文件時,做準備工作
      image_writer_.reset(new ImageWriter(*driver_, image_base_, compiler_options_->GetCompilePic(),IsAppImage(), image_storage_mode_, oat_filenames_, dex_file_oat_index_map_));
      if (!image_writer_->PrepareImageAddressSpace()) {
        LOG(ERROR) << "Failed to prepare image address space.";
        return false;
      }
    }
        oat_writer->PrepareLayout(driver_.get(), image_writer_.get(), dex_files, &patcher);
        size_t rodata_size = oat_writer->GetOatHeader().GetExecutableOffset();
        size_t text_size = oat_writer->GetSize() - rodata_size;
        elf_writer->SetLoadedSectionSizes(rodata_size, text_size, oat_writer->GetBssSize());
        if (!oat_writer->WriteRodata(rodata)) {
          LOG(ERROR) << "Failed to write .rodata section to the ELF file " << oat_file->GetPath();
          return false;
        }
        OutputStream* text = elf_writer->StartText();
        if (!oat_writer->WriteCode(text)) {
          LOG(ERROR) << "Failed to write .text section to the ELF file " << oat_file->GetPath();
          return false;
        }
        if (!oat_writer->WriteHeader(elf_writer->GetStream(),
                                     image_file_location_oat_checksum_,
                                     image_file_location_oat_data_begin_,
                                     image_patch_delta_)) {
          LOG(ERROR) << "Failed to write oat header to the ELF file " << oat_file->GetPath();
          return false;
        }
}

OAT文件的寫入流程就是按照這幾個步驟完成,可以參照oat文件的加載完成OAT文件格式的詳細瞭解。

另外,在WriteOatFiles過程中,如果發現本次要生成image,則會提前準備 image address space:

PrepareImageAddressSpace() {
    PruneNonImageClasses();  // 把不準備添加到 app-image 中的 class 清除,比如claspath中的class,profile中記錄class之外的class等
    heap->CollectGarbage(false);  // Remove garbage.這一步中回收的對象包括 上面被刪除的class 引用的對象,比如該class對應的DexCache
    CalculateNewObjectOffsets();//重新計算 object offset
  if (!AllocMemory()) { // 分配內存,存放image信息
    return false;
  }
}

在 CalculateNewObjectOffsets()函數中,會計算DexCache Array在 oat中的offset,並做安全檢查:

void ImageWriter::PrepareDexCacheArraySlots() {
  for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) {
    mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root));
    if (dex_cache == nullptr || IsInBootImage(dex_cache)) {
      continue;
    }
    const DexFile* dex_file = dex_cache->GetDexFile();
    CHECK(dex_file_oat_index_map_.find(dex_file) != dex_file_oat_index_map_.end())  << "Dex cache should have been pruned " << dex_file->GetLocation()  << "; possibly in class path";
    DexCacheArraysLayout layout(target_ptr_size_, dex_file);
    size_t oat_index = GetOatIndexForDexCache(dex_cache);
    ImageInfo& image_info = GetImageInfo(oat_index);
    uint32_t start = image_info.dex_cache_array_starts_.Get(dex_file);
    AddDexCacheArrayRelocation(dex_cache->GetResolvedTypes(), start + layout.TypesOffset(), dex_cache);
    AddDexCacheArrayRelocation(dex_cache->GetResolvedMethods(),  start + layout.MethodsOffset(), dex_cache);
    AddDexCacheArrayRelocation(dex_cache->GetResolvedFields(),  start + layout.FieldsOffset(),  dex_cache);
    AddDexCacheArrayRelocation(dex_cache->GetStrings(), start + layout.StringsOffset(), dex_cache);
  }
}

前面提到過,只會把 App自身的 class 放到App-image中,並不包括 class path對應的dex中的 class,由於class引用自己對應的DexCache,

而app自身class之外的class,在PruneNonImageClasses()被清空,所以對應的DexCache也被GC掉,此時,Runtime中存在的DexCache應該只有 app自身dex對應的DexCache;

所以這裏檢查當前 runtime中的所有dexcache,是否都是 dex_file_oat_index_map_中dexfile的對應,如果不是對應,則說明出問題了;

dex_file_oat_index_map_ 是在dex2oat Setup函數中填充的; 其填充的dexfile就是 dex2oat參數中傳遞過來的 app自身的dex文件。



5. app-image如何使用

app-image在App啓動的過程中加載,加載流程如下:

->ActivityThread.main()
->...
->ActivityThread.performLaunchActivity()
->ActivityClientRecord.packageInfo.getClassLoader()
->LoadedApk.getClassLoader()
->LoadedApk.createOrUpdateClassLoaderLocked()
->ApplicationLoaders.getDefault().getClassLoader()
->new PathClassLoader()
->new BaseDexClassLoader()
->new DexPathList()
->makePathElements
->loadDexFile
->new DexFile()
->openDexFile()
->openDexFileNative
->openDexFilesFromOat()
->OpenImageSpace(source_oat_file)// 在這裏嘗試打開oat文件對應的image文件,
-> heap ->AddSpace(image_space);
-> class_linker ->AddImageSpace(image-space)

class_linker的 AddImageSpace中會調用 UpdateAppImageClassLoadersAndDexCaches()方法:

bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches(
    ...
    ClassTable* table = InsertClassTableForClassLoader(class_loader.Get());
    for (size_t i = 0; i < num_dex_caches; i++) {
      mirror::DexCache* const dex_cache = dex_caches->Get(i);
      const DexFile* const dex_file = dex_cache->GetDexFile();
      RegisterDexFileLocked(*dex_file, hs3.NewHandle(dex_cache));
      GcRoot<mirror::Class>* const types = dex_cache->GetResolvedTypes();
      const size_t num_types = dex_cache->NumResolvedTypes();
        for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) {
          mirror::Class* klass = types[j].Read();
          if (space->HasAddress(klass)) {
            klass->SetClassLoader(class_loader.Get());
          }
          table->Insert(klass);
        }
      ...
    }

在這個函數中,會把app-image中的所有類的 classLoader更新爲當前的 classLoader,並將它們添加到當前的ClassLoader的class table中;

之後在當前進程中有使用相關類時,在FindClass過程中,直接就能在 class table中找到,即可使用,免去了類的加載。

至此,app進程在後續的運行中,就可以直接使用app-image中的類了。

發佈了34 篇原創文章 · 獲贊 13 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章