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);
  }
}

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值