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