位置:首页 » 技术 » 也来看看Android的ART运行时

也来看看Android的ART运行时

日期:2015-12-08 阅读:0num
Advertisement

之前因为需要,研究了一下ART的相关源码,也做了一些记录与总结,现在重新整理了一下与大家共同讨论和交流一下。

0x00 概述

ART是Android平台上的新一代运行时,用来代替dalvik。它主要采用了AOT的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行,节省了运行时间,提高了效率,但是在一定程度上使得安装的时间变长,空间占用变大。

从Android的源码上看,ART相关的内容主要有compiler和与之相关的程序dex2oat、runtime、Java调试支持和对oat文件进行解析的工具oatdump。

下面这张图是ART的源码目录结构:

也来看看Android的ART运行时

中间有几个目录比较关键,

首先是dex2oat,负责将dex文件给转换为oat文件,具体的翻译工作需要由compiler来完成,最后编译为dex2oat;

其次是runtime目录,内容比较多,主要就是运行时,编译为libart.so用来替换libdvm.so,dalvik是一个外壳,其中还是在调用ART runtime;

oatdump也是一个比较重要的工具,编译为oatdump程序,主要用来对oat文件进行分析并格式化显示出文件的组成结构;

jdwpspy是java的调试支持部分,即JDWP服务端的实现。

0x01 oat文件

oat文件的格式,可以从dex2oat和oatdump两个目录入手。简单的说,oat文件是嵌套在一个elf文件的格式中的。在elf文件的动态符号表中有三个重要的符号:oatdata、oatexec、oatlastword,分别表示oat的数据区,oat文件中的native code和结束位置。这些关系结构在图中说明的很清楚,简单理解就是在oatdata中,保存有原来的dex文件内容,在头部还保留了寻址到dex文件内容的偏移地址和指向对应的oat class偏移,oat class中还保存了对应的native code的偏移地址,这样也就间接的完成了dexbytecode和native code的对应关系。

具体的一些代码可以参考 /art/dex2oat/dex2oat.cc 中的 static int dex2oat(intargc, char** argv) 函数和 /art/oatdump/oatdump.ccstatic intoatdump(intargc, char** argv) 的函数,可以很快速的理解oat文件的格式和解析。在 /art/compiler/elf_writer_quick.cc 中的 Write 函数很值得参考。

0x02 运行时的启动

ART运行时的启动过程很早,是由zygote所启动的,与dalvik的启动过程完全一样,保证了由dalvik到ART的无缝衔接。

整个启动过程是从 app_processs(/frameworks/base/cmds/app_process/app_main.cpp) 开始的,创建了一个对象AppRuntime runtime,这个是一个单例,整个系统运行时只有一个。随着zygote的fork过程,只是在不断地复制指向这个对象的指针个每个子进程。然后就开始执行runtime.start方法。这个方法里先调用startVm启动虚拟机。是由 JNI_CreateJavaVM 方法具体执行的的,即 /art/runtime/jni_internal.ccextern "C" jintJNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) 。然后调用startReg注册一些native的method。在最后比较重要的是查找到要执行的java代码的main方法,然后执行进入托管代码的世界,这也是我们感兴趣的地方。

也来看看Android的ART运行时

如图,最后调用的是CallStaticVoidMethod,去看看它的实现:

也来看看Android的ART运行时

再去寻找InvokeWithVarArgs:

也来看看Android的ART运行时

跳到InvokeWithArgArray:

也来看看Android的ART运行时

可以看到一个很关键的class:

也来看看Android的ART运行时

即ArtMethod,它的一个成员方法就是负责调用oat文件中的native code的:

也来看看Android的ART运行时

最后这就是最终的入口:

也来看看Android的ART运行时

283行的blxip指令就是最终进入native code的位置。可以大致得到结论,通过查找相关的oat文件,得到所需要的类和方法,并将其对应的native code的位置放入ArtMethod结构,最后通过Invoke成员完成调用。下一步的工作需要着重关注的便是native code代码调用其他的java方法时如何去通过运行时定位和跳转的。

注意注释中描述了ART下的ABI,与标准的ARM调用约定相似,但是R0存放的是调用者的方法的ArtMethod对象地址,R0-R3包含的才是参数,包括this。多余的存放在栈中,从SP+16的位置开始。返回值同样通过R0/R1传递。R9指向运行时分配的当前的线程对象指针。

0x03 类加载

类加载的任务主要由ClassLinker类来负责,先看一下这个过程的顺序图:

也来看看Android的ART运行时

顺序图中以静态成员的初始化和虚函数的初始化为例,描述了调用的逻辑。下面进行详细的叙述。

从FindClass开始:

#!java
mirror::Class* ClassLinker::FindClass(constchar* descriptor, mirror::ClassLoader* class_loader) {
……
mirror::Class* klass = LookupClass(descriptor, class_loader);
if (klass != NULL) {
returnEnsureResolved(self, klass);
  }
if (descriptor[0] == '[') {
returnCreateArrayClass(descriptor, class_loader);   

  } elseif (class_loader == NULL) {
DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_);
if (pair.second != NULL) {
returnDefineClass(descriptor, NULL, *pair.first, *pair.second);
    }
……
}

省略次要的代码,首先利用LookupClass查找所需要的类是否被加载,对于此场景所以不符合此条件。然后判断是否是数组类型的类,也跳过此分支,进入到我们最感兴趣的DefineClass中。

#!java
mirror::Class* ClassLinker::DefineClass(constchar* descriptor,
                                        mirror::ClassLoader* class_loader,
constDexFile&dex_file,
constDexFile::ClassDef&dex_class_def) {
……
SirtRef<mirror::Class>klass(self, NULL);
if (UNLIKELY(!init_done_)) {
// finish up init of hand crafted class_roots_
if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
klass.reset(GetClassRoot(kJavaLangObject));
    } elseif (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
klass.reset(GetClassRoot(kJavaLangClass));
    } elseif (strcmp(descriptor, "Ljava/lang/String;") == 0) {
klass.reset(GetClassRoot(kJavaLangString));
    } elseif (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
klass.reset(GetClassRoot(kJavaLangDexCache));
    } elseif (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtField));
    } elseif (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtMethod));
    } else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
    }
  } else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
  }
klass->SetDexCache(FindDexCache(dex_file));
LoadClass(dex_file, dex_class_def, klass, class_loader);
……
returnklass.get();
}

拣重要的部分看,这个方法基本上完成了两个个功能,即从dex文件加载类和加载过的类插入一个表中,供LookupClass查询。

我们关注第一个功能,首先是进行一些内置类的判断,对于自定义的类则是手动分配空间、,然后查找相关的dex文件,最后进行加载。

接着看LoadClass方法:

#!java
voidClassLinker::LoadClass(constDexFile&dex_file,
constDexFile::ClassDef&dex_class_def,
SirtRef<mirror::Class>&klass,
                            mirror::ClassLoader* class_loader) {
……
// Load fields fields.
const byte* class_data = dex_file.GetClassData(dex_class_def);
if (class_data == NULL) {
return;  // no fields or methods - for example a marker interface
  }
ClassDataItemIteratorit(dex_file, class_data);
  Thread* self = Thread::Current();
if (it.NumStaticFields() != 0) {
    mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields());
if (UNLIKELY(statics == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetSFields(statics);
  }
if (it.NumInstanceFields() != 0) {
    mirror::ObjectArray<mirror::ArtField>* fields =
AllocArtFieldArray(self, it.NumInstanceFields());
if (UNLIKELY(fields == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetIFields(fields);
  }
for (size_ti = 0; it.HasNextStaticField(); i++, it.Next()) {
SirtRef<mirror::ArtField>sfield(self, AllocArtField(self));
if (UNLIKELY(sfield.get() == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetStaticField(i, sfield.get());
LoadField(dex_file, it, klass, sfield);
  }
for (size_ti = 0; it.HasNextInstanceField(); i++, it.Next()) {
SirtRef<mirror::ArtField>ifield(self, AllocArtField(self));
if (UNLIKELY(ifield.get() == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetInstanceField(i, ifield.get());
LoadField(dex_file, it, klass, ifield);
  } 

UniquePtr<constOatFile::OatClass>oat_class;
if (Runtime::Current()->IsStarted() && !Runtime::Current()->UseCompileTimeClassPath()) {
oat_class.reset(GetOatClass(dex_file, klass->GetDexClassDefIndex()));
  } 

// Load methods.
if (it.NumDirectMethods() != 0) {
// TODO: append direct methods to class object
    mirror::ObjectArray<mirror::ArtMethod>* directs =
AllocArtMethodArray(self, it.NumDirectMethods());
if (UNLIKELY(directs == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetDirectMethods(directs);
  }
if (it.NumVirtualMethods() != 0) {
// TODO: append direct methods to class object
    mirror::ObjectArray<mirror::ArtMethod>* virtuals =
AllocArtMethodArray(self, it.NumVirtualMethods());
if (UNLIKELY(virtuals == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetVirtualMethods(virtuals);
  }
size_tclass_def_method_index = 0;
for (size_ti = 0; it.HasNextDirectMethod(); i++, it.Next()) {
SirtRef<mirror::ArtMethod>method(self, LoadMethod(self, dex_file, it, klass));
if (UNLIKELY(method.get() == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetDirectMethod(i, method.get());
if (oat_class.get() != NULL) {
LinkCode(method, oat_class.get(), class_def_method_index);
    }
method->SetMethodIndex(class_def_method_index);
class_def_method_index++;
  }
for (size_ti = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
SirtRef<mirror::ArtMethod>method(self, LoadMethod(self, dex_file, it, klass));
if (UNLIKELY(method.get() == NULL)) {
CHECK(self->IsExceptionPending());  // OOME.
return;
    }
klass->SetVirtualMethod(i, method.get());
    DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i);
if (oat_class.get() != NULL) {
LinkCode(method, oat_class.get(), class_def_method_index);
    }
class_def_method_index++;
  }
……
}

为了弄清这个方法,我们先得看看Class类利用了什么重要的成员:

#!java
ObjectArray<ArtMethod>* direct_methods_;
// instance fields
// specifies the number of reference fields.
ObjectArray<ArtField>* ifields_;
// For every interface a concrete class implements, we create an array of the concrete vtable_
// methods for the methods in the interface.
IfTable* iftable_;
// Static fields
ObjectArray<ArtField>* sfields_;
// The superclass, or NULL if this is java.lang.Object, an interface or primitive type.
// Virtual methods defined in this class; invoked through vtable.
ObjectArray<ArtMethod>* virtual_methods_;
// Virtual method table (vtable), for use by "invoke-virtual".  The vtable from the superclass is
// copied in, and virtual methods from our class either replace those from the super or are
// appended. For abstract classes, methods may be created in the vtable that aren't in
// virtual_ methods_ for miranda methods.
ObjectArray<ArtMethod>* vtable_;
// Total size of the Class instance; used when allocating storage on gc heap.
// See also object_size_.
size_tclass_size_;

这样就比较清晰了。LoadClass首先读取dex文件中的classdata,然后初始化一个迭代器来对classdata中的数据进行遍历。接下来分部分进行:

分配一个对象ObjectArray来表示静态成员,并利用静态成员的数量初始化,并将这个对象的地址赋值给Class的 sfields_ 成员。

同样的完成Class的ifields_成员的初始化,用来表示私有数据成员

接下来,遍历静态成员,对于每个成员分配一个Object对象,然后将地址放入之前分配的ObjectArray数组中,并将dex文件中的相关信息加载到Object对象中,从而完成了静态成员信息的读取。

同理,完成了私有成员信息的读取。

像对于数据成员一样,分配一个ObjectArray用于表示directmethod,并用于初始化 direct_methods_ 成员。

同理,初始化了 virtual_methods_ 成员。

遍历directmethod成员,对于每一个directmethod生成一个ArtMethod对象,并在构造函数中通过LoadMethod完成dex文件中相应信息的读取。再将ArtMethod对象放入之前的ObjectArray中,还需要利用LinkCode将实际的方法代码起始地址用来初始化ArtMethod的 entry_point_from_compiled_code_ 成员,最后更新每个ArtMethod的 method_index_ 成员用于方法索引查找。

同样的过程完成了对于VirtualMethod的处理

最终就完成了类的加载。

下面需要再关注一下一个类实例化的过程。

类的实例化是通过TLS(线程局部存储)中的一个函数表中的pAllocObject来进行的。pAllocObject这个函数指针被指向了 art_quick_alloc_object 函数。这个函数是与硬件相关的,实际上它又调用了artAllocObjectFromCode函数,又调用了AllocObjectFromCode函数,在完成了一系列检查判断后调用了Class::AllocObject,这个方法很简单,就是一句话:

returnRuntime::Current()->GetHeap()->AllocObject(self, this, this->object_size_)

其实是在堆上根据之前LoadClass时指定的类对象的大小分配了一块内存,按照一个Object对象指针返回。

可以以图形来展示一下:

也来看看Android的ART运行时

看一下最后调用的这个函数:

#!java
mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_tbyte_count) {
……
obj = Allocate(self, alloc_space_, byte_count, &bytes_allocated);
 ……
if (LIKELY(obj != NULL)) {
obj->SetClass(c);
   ……
returnobj;
  } else {
   ……
}

在这个函数中分配了内存空间之后,还调用了SetClass这个关键的函数,把Object对象中的klass_成员利用LoadClass的结果初始化了。

这样的话一个完整的类的实例化的内存结构就如图所示了:

也来看看Android的ART运行时

0x04 编译过程

关于ART的编译过程,主要是由dex2oat程序启动的,所以可以从dex2oat入手,先画出整个过程的顺序图。

也来看看Android的ART运行时

上图是第一阶段的流程,主要是由dex2oat调用编译器的过程。

也来看看Android的ART运行时

第二阶段主要是进入编译器的处理流程,通过对dalvik指令进行一次编译为MIR,然后二次编译为LIR,最后编译成ARM指令。

下面择要对关键代码进行整理:

#!java
staticintdex2oat(intargc, char** argv){
……
UniquePtr<constCompilerDriver>compiler(dex2oat->CreateOatFile(boot_image_option,
host_prefix.get(),
android_root,
is_host,
dex_files,
oat_file.get(),
bitcode_filename,
image,
image_classes,
dump_stats,
timings));
……
}

在这个函数的调用中,主要进行的多线程进行编译

#!java
voidCompilerDriver::CompileAll(jobjectclass_loader,
conststd::vector<constDexFile*>&dex_files,
base::TimingLogger&timings)
{
……
Compile(class_loader, dex_files, *thread_pool.get(), timings);
……
}   

voidCompilerDriver::Compile(jobjectclass_loader,
conststd::vector<constDexFile*>&dex_files,
ThreadPool&thread_pool, base::TimingLogger&timings) {
……
CompileDexFile(class_loader, *dex_file, thread_pool, timings);
……
}

一直到

#!java
voidCompilerDriver::CompileDexFile(jobjectclass_loader,
constDexFile&dex_file,ThreadPool&thread_pool,
base::TimingLogger&timings) {
……
context.ForAll(0, dex_file.NumClassDefs(),
CompilerDriver::CompileClass, thread_count_);
……
}

启动了多线程,执行CompilerDriver::CompileClass函数进行真正的编译过程。

#!java
voidCompilerDriver::CompileClass(constParallelCompilationManager* manager, size_tclass_def_index) {
……
ClassDataItemIteratorit(dex_file, class_data);
CompilerDriver* driver = manager->GetCompiler();
int64_tprevious_direct_method_idx = -1;
while (it.HasNextDirectMethod()) {
uint32_tmethod_idx = it.GetMemberIndex();
if (method_idx == previous_direct_method_idx) {
it.Next();
continue;
    }
previous_direct_method_idx = method_idx;
driver->CompileMethod(it.GetMethodCodeItem(),
    it.GetMemberAccessFlags(),
    it.GetMethodInvokeType(class_def),class_def_index,
    method_idx, jclass_loader, dex_file,
    dex_to_dex_compilation_level);
it.Next();
  }
int64_tprevious_virtual_method_idx = -1;
while (it.HasNextVirtualMethod()) {
uint32_tmethod_idx = it.GetMemberIndex();
if (method_idx == previous_virtual_method_idx) {
it.Next();
continue;
    }
previous_virtual_method_idx = method_idx;
driver->CompileMethod(it.GetMethodCodeItem(),
    it.GetMemberAccessFlags(),
    it.GetMethodInvokeType(class_def), class_def_index,
    method_idx, jclass_loader, dex_file,
    dex_to_dex_compilation_level);
it.Next();
  }

主要过程就是通过读取class中的数据,利用迭代器遍历每个DirectMethod和VirtualMethod,然后分别对每个Method作为单元利用CompilerDriver::CompileMethod进行编译。

CompilerDriver::CompileMethod函数主要是调用了CompilerDriver::CompilerDriver* constcompiler_这个成员变量(函数指针)。

这个变量是在CompilerDriver的构造函数中初始化的,根据不同的编译器后端选择不同的实现,不过基本上的流程都是一样的,通过对Portable后端的分析,可以看到最后调用的是static CompiledMethod* CompileMethod函数。

#!java
staticCompiledMethod* CompileMethod(CompilerDriver&compiler,
constCompilerBackendcompiler_backend,
constDexFile::CodeItem* code_item,
uint32_taccess_flags, InvokeTypeinvoke_type,
uint16_tclass_def_idx, uint32_tmethod_idx,
jobjectclass_loader, constDexFile&dex_file
#ifdefined(ART_USE_PORTABLE_COMPILER)
 , llvm::LlvmCompilationUnit* llvm_compilation_unit
#endif
) {
……
    cu.mir_graph.reset(newMIRGraph(&cu, &cu.arena));
    cu.mir_graph->InlineMethod(code_item, access_flags, invoke_type, class_def_idx, method_idx,class_loader, dex_file);
    cu.mir_graph->CodeLayout();
    cu.mir_graph->SSATransformation();
    cu.mir_graph->PropagateConstants();
    cu.mir_graph->MethodUseCount();
    cu.mir_graph->NullCheckElimination();
    cu.mir_graph->BasicBlockCombine();
    cu.mir_graph->BasicBlockOptimization();
    ……
    cu.cg.reset(ArmCodeGenerator(&cu, cu.mir_graph.get(), &cu.arena));
    ……
    cu.cg->Materialize();
    result = cu.cg->GetCompiledMethod();
    returnresult;
}

在这个过程中牵涉了几种重要的数据结构:

#!java
classMIRGraph {
……
BasicBlock* entry_block_;
BasicBlock* exit_block_;
BasicBlock* cur_block_;
intnum_blocks_;
 ……
}
structBasicBlock {
  ……
MIR* first_mir_insn;
MIR* last_mir_insn;
BasicBlock* fall_through;
BasicBlock* taken;
BasicBlock* i_dom;                // Immediate dominator.
 ……
};
structMIR {
DecodedInstructiondalvikInsn;
……
MIR* prev;
MIR* next;
 ……
};
structDecodedInstruction {
uint32_tvA;
uint32_tvB;
uint64_tvB_wide;        /* for k51l */
uint32_tvC;
uint32_targ[5];         /* vC/D/E/F/G in invoke or filled-new-array */
Instruction::Codeopcode;    

explicitDecodedInstruction(constInstruction* inst) {
inst->Decode(vA, vB, vB_wide, vC, arg);
opcode = inst->Opcode();
  }
};

这几个数据结构的关系如图所示:

也来看看Android的ART运行时

简单地说,一个MIRGraph对应着一个编译单元即一个方法,对一个方法进行控制流分析,划分出BasicBlock,并在BasicBlock中的fall_through和taken域中指向下一个BasicBlock(适用于分支出口)。每一个BasicBlock包含若干dalvik指令,每一天dalvik指令被翻译为若干MIR语句,这些MIR结构体之间形成双向链表。每一个BasicBlock也指示了第一条和最后一条MIR语句。

InlineMethod函数主要是解析一个方法,并划分BasicBlock边界,但是只是简单地把BasicBlock连接成一个链表,利用fall_through指示。

在CodeLayout函数中具体地再次遍历BasicBlock链表,并根据每个BasicBlock出口的指令,再次调整taken域和fall_through域,形成完整的控制流图结构。

SSATransformation函数是对每条指令进行静态单赋值变换。先对控制流图进行深度优先遍历,并计算出BasicBlock之间的支配关系,插入Phi函数,并对变量进行命名更新。

其余的方法主要是一些代码优化过程,例如常量传播、消除空指针检查;并在BasicBlock组合之后再进行BasicBlock的优化,消除冗余指令。

这样基本上就完成了MIR的生成过程,在某种程度上,可以认为MIR即为对dalvik指令进行SSA变换之后的指令形态。

接着就调用cu.cg->Materialize()用来产生最终代码。cu.cg在之前的代码被指向了Mir2Lir对象,所以调用的是:

#!java
voidMir2Lir::Materialize() {
CompilerInitializeRegAlloc();  // Needs to happen after SSA naming  

/* Allocate Registers using simple local allocation scheme */
SimpleRegAlloc();   

 ……
/* Convert MIR to LIR, etc. */
if (first_lir_insn_ == NULL) {
MethodMIR2LIR();
  } 

/* Method is not empty */
if (first_lir_insn_) {
// mark the targets of switch statement case labels
ProcessSwitchTables();  

/* Convert LIR into machine code. */
AssembleLIR();  

  ……
  }
}

其中重要的两个调用就是MethodMIR2LIR()和AssembleLIR()。

#!java
MethodMIR2LIR将MIR转化为LIR,遍历每个BasicBlock,对每个基本块执行MethodBlockCodeGen,本质上最后是执行了CompileDalvikInstruction。    

voidMir2Lir::CompileDalvikInstruction(MIR* mir, BasicBlock* bb, LIR* label_list) {
  ……
Instruction::Codeopcode = mir->dalvikInsn.opcode;
intopt_flags = mir->optimization_flags;
uint32_tvB = mir->dalvikInsn.vB;
uint32_tvC = mir->dalvikInsn.vC;    

……
switch (opcode) {
case XXX:
GenXXXXXX(……)
default:
LOG(FATAL) <<"Unexpected opcode: "<<opcode;
  }
}

也就是通过解析指令,然后根据opcode进行分支判断,调用最终不同的指令生成函数。最后将LIR之间也形成一个双向链表。

AssembleLIR最终调用的是AssembleInstructions函数。程序中维护了一个编码指令表ArmMir2Lir::EncodingMap,AssembleInstructions即是通过查找这个表来进行翻译,将LIR转化为了ARM指令,并将所翻译的指令存储到CodeBufferMir2Lir::code_buffer_之中。

这样就完成了一次编译的完整流程。

0x05 JNI分析

ART环境中的JNI接口与Dalvik同样符合JVM标准,但是其中的实现却有所不同。以下通过三个过程来进行简述。

1、类加载初始化

首先观察一个native的java成员方法通过dex2oat编译后的结果:

java.lang.Stringcom.example.hellojni.HelloJni.stringFromJNI() (dex_method_idx=9)
    DEX CODE:
    CODE: 0xb6bfd1ac (offset=0x000011ac size=148)...
      0xb6bfd1ac: e92d4de0  stmdbsp!, {r5, r6, r7, r8, r10, r11, lr}
      0xb6bfd1b0: e24dd024  sub     sp, sp, #36
      0xb6bfd1b4: e58d0000  str     r0, [sp, #0]
      0xb6bfd1b8: e58d1044  str     r1, [sp, #68]
      0xb6bfd1bc: e3a0c001  mov    r12, r0, #1
      0xb6bfd1c0: e58dc004  str     r12, [sp, #4]
      0xb6bfd1c4: e599c074  ldr     r12, [r9, #116]  ;top_sirt_
      0xb6bfd1c8: e58dc008  str     r12, [sp, #8]
      0xb6bfd1cc: e28dc004  add    r12, sp, #4
      0xb6bfd1d0: e589c074  str     r12, [r9, #116]  ;top_sirt_
      0xb6bfd1d4: e59dc044  ldr     r12, [sp, #68]
      0xb6bfd1d8: e58dc00c  str     r12, [sp, #12]
      0xb6bfd1dc: e589d01c  strsp, [r9, #28]  ; 28
      0xb6bfd1e0: e3a0c000  mov    r12, r0, #0
      0xb6bfd1e4: e589c020  str     r12, [r9, #32]  ; 32
      0xb6bfd1e8: e1a00009  mov    r0, r9
      0xb6bfd1ec: e590c1b8  ldr  r12, [r0, #440] //qpoints->pJniMethodStart = JniMethodStart
      0xb6bfd1f0: e12fff3c  blx     r12
      0xb6bfd1f4: e58d0010  str     r0, [sp, #16]
      0xb6bfd1f8: e28d100c  add     r1, sp, #12
      0xb6bfd1fc: e5990024  ldr     r0, [r9, #36]  ;jni_env_
      0xb6bfd200: e59dc000  ldr     r12, [sp, #0]
      0xb6bfd204: e59cc048  ldr     r12, [r12, #72]
      0xb6bfd208: e12fff3c  blx     r12    // const void* ArtMethod::native_method_
      0xb6bfd20c: e59d1010  ldr     r1, [sp, #16]
      0xb6bfd210: e1a02009  mov    r2, r9
      0xb6bfd214: e592c1c8  ldr     r12, [r2, #456]
      0xb6bfd218: e12fff3c  blx r12//qpoints->pJniMethodEndWithReference= JniMethodEndWithReference
      0xb6bfd21c: e599c00c  ldr     r12, [r9, #12]  ; exception_
      0xb6bfd220: e35c0000  cmp     r12, #0
      0xb6bfd224: 1a000001  bne     +4 (0xb6bfd230)
      0xb6bfd228: e28dd03c  add     sp, sp, #60
      0xb6bfd22c: e8bd8000  ldmiasp!, {pc}
      0xb6bfd230: e1a0000c  mov     r0, r12
      0xb6bfd234: e599c260  ldr     r12, [r9, #608]  ;pDeliverException
      0xb6bfd238: e12fff3c  blx     r12
      0xb6bfd23c: e1200070  bkpt    #0

可以看到,它没有对应的dex code。

用伪码表示这个过程:

JniMethodStart(Thread*);
ArtMethod ::native_method_(…..);
JniMethodEndWithReference(……);
return;

基本上就是这三个函数的调用。

但是从ART的LoadClass的函数来分析,ArtMethod对象与真实执行的代码链接的过程主要是通过LinkCode函数执行的。

#!java
staticvoidLinkCode(SirtRef<mirror::ArtMethod>&method, constOatFile::OatClass* oat_class,
uint32_tmethod_index)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
constOatFile::OatMethodoat_method = oat_class->GetOatMethod(method_index);
oat_method.LinkMethod(method.get());    

Runtime* runtime = Runtime::Current();
boolenter_interpreter = NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
if (enter_interpreter) {  method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
} else{ method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
 }  

if (method->IsAbstract()) { method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
return;
  } 

if (method->IsStatic() && !method->IsConstructor()) {
method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
  } elseif (enter_interpreter) {
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
  } 

if (method->IsNative()) {
method->UnregisterNative(Thread::Current());
  } 

runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
method->GetEntryPointFromCompiledCode());
}

可以看到,在LinkCode的开始就将通过oat_method.LinkMethod(method.get())将对象与代码进行了链接,但是在后边又针对几种特殊情况做了一些处理,包括解释执行入口和静态方法等等。我们主要关注的是JNI方法,即

if (method->IsNative()) {
method->UnregisterNative(Thread::Current());
  }

展开函数:

#!java
voidArtMethod::UnregisterNative(Thread* self) {
CHECK(IsNative()) <<PrettyMethod(this);
RegisterNative(self, GetJniDlsymLookupStub());
}   

extern"C"void* art_jni_dlsym_lookup_stub(JNIEnv*, jobject);
staticinlinevoid* GetJniDlsymLookupStub() {
returnreinterpret_cast<void*>(art_jni_dlsym_lookup_stub);
}   

voidArtMethod::RegisterNative(Thread* self, constvoid* native_method) {
DCHECK(Thread::Current() == self);
CHECK(IsNative()) <<PrettyMethod(this);
CHECK(native_method != NULL) <<PrettyMethod(this);
if (!self->GetJniEnv()->vm->work_around_app_jni_bugs) {
SetNativeMethod(native_method);
  } else {
SetNativeMethod(reinterpret_cast<void*>(art_work_around_app_jni_bugs));
SetFieldPtr<constuint8_t*>(OFFSET_OF_OBJECT_MEMBER(ArtMethod, gc_map_),
reinterpret_cast<constuint8_t*>(native_method), false);
  }
}   

voidArtMethod::SetNativeMethod(constvoid* native_method) {
SetFieldPtr<constvoid*>(OFFSET_OF_OBJECT_MEMBER(ArtMethod, native_method_),
native_method, false);
}

很清晰可以看到,在类加载的时候是把ArtMethod的 native_method_ 成员设置为了 art_jni_dlsym_lookup_stub 函数,那么在执行JNI方法的时候就会执行 art_jni_dlsym_lookup_stub 函数。

2、通过java调用JNI方法

art_jni_dlsym_lookup_stub 函数入手,这个函数使用汇编写的,与具体的平台相关。

ENTRYart_jni_dlsym_lookup_stub
push   {r0, r1, r2, r3, lr}           @ spillregs
    .save  {r0, r1, r2, r3, lr}
    .pad #20
    .cfi_adjust_cfa_offset20
subsp, #12                        @ padstackpointertoalignframe
    .pad #12
    .cfi_adjust_cfa_offset12
blxartFindNativeMethod
movr12, r0                        @ saveresultinr12
addsp, #12                        @ restorestackpointer
    .cfi_adjust_cfa_offset -12
cbzr0, 1f                         @ ismethodcodenull?
pop    {r0, r1, r2, r3, lr}           @ restoreregs
    .cfi_adjust_cfa_offset -20
bxr12                            @ ifnon-null, tailcalltomethod's code
1:
    .cfi_adjust_cfa_offset 20
pop    {r0, r1, r2, r3, pc}           @ restore regs and return to caller to handle exception
    .cfi_adjust_cfa_offset -20
END art_jni_dlsym_lookup_stub

主要的过程就是先调用artFindNativeMethod得到真正的native code的地址,然后在跳转到相应地址去执行,即对应了

blxartFindNativeMethod
bxr12                            @ ifnon-null, tailcalltomethod's code

两条指令。

#!java
extern"C"void* artFindNativeMethod() {
Thread* self = Thread::Current();
Locks::mutator_lock_->AssertNotHeld(self);
ScopedObjectAccesssoa(self);    

mirror::ArtMethod* method = self->GetCurrentMethod(NULL);
DCHECK(method != NULL); 

void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
if (native_code == NULL) {
DCHECK(self->IsExceptionPending());
returnNULL;
  } else {
method->RegisterNative(self, native_code);
returnnative_code;
  }
}

主要的过程也就是查找到相应方法的native code,然后再次设置 ArtMethod的native_method_ 成员,这样以后再执行的时候就直接跳到了native code执行了。

3、Native方法中调用java方法

这个主要是通过JNIEnv来间接调用的。JNIEnv中维持了许多JNI API可以被native code来使用。C和C++的实现形式略有不同,C++是对C的事先进行了一个简单的包装,具体可以参见jni.h。这里为了便于叙述以C为例。

#!java
typedefconststructJNINativeInterface* JNIEnv;
structJNINativeInterface {
void*       reserved0;
void*       reserved1;
void*       reserved2;
void*       reserved3;
jint        (*GetVersion)(JNIEnv *);
jclass      (*DefineClass)(JNIEnv*, constchar*, jobject, constjbyte*,  jsize);
jclass      (*FindClass)(JNIEnv*, constchar*);
…………
…………
jobject     (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

这些API以函数指针的形式存在,并在libart.so中实现,在整个art的初始化的过程中进行了对应。

在libart.so中的对应:

#!java
constJNINativeInterfacegJniNativeInterface = {
NULL,  // reserved0.
NULL,  // reserved1.
NULL,  // reserved2.
NULL,  // reserved3.
JNI::GetVersion,
JNI::DefineClass,
JNI::FindClass,
…………
…………
JNI::NewDirectByteBuffer,
JNI::GetDirectBufferAddress,
JNI::GetDirectBufferCapacity,
JNI::GetObjectRefType,
};

下面以一个常见的native code调用java的过程进行下分析:

(*pEnv)->FindClass(……);
getMethodID(……);
(*pEnv)->CallVoidMethod(……);

即查找类,得到相应的方法的ID,然后通过此ID去调用。

#!java
staticjclassFindClass(JNIEnv* env, constchar* name) {
CHECK_NON_NULL_ARGUMENT(FindClass, name);
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
std::stringdescriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccesssoa(env);
Class* c = NULL;
if (runtime->IsStarted()) {
ClassLoader* cl = GetClassLoader(soa);
      c = class_linker->FindClass(descriptor.c_str(), cl);
    } else {
      c = class_linker->FindSystemClass(descriptor.c_str());
    }
returnsoa.AddLocalReference<jclass>(c);
  }

可以看到JNI中的FindClass实际调用的是ClassLinker::FindClass,这与ART的类加载过程一致。

#!java
staticvoidCallVoidMethod(JNIEnv* env, jobjectobj, jmethodIDmid, ...) {
va_listap;
va_start(ap, mid);
CHECK_NON_NULL_ARGUMENT(CallVoidMethod, obj);
CHECK_NON_NULL_ARGUMENT(CallVoidMethod, mid);
ScopedObjectAccesssoa(env);
InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap);
va_end(ap);
  }

最后调用的是ArtMethod::Invoke()。

可以说如出一辙,即JNI的这些API其实还是做了一遍ART的类加载和初始化及调用的过程。

0x06 总结与补充

  1. oat文件作为一个静态库的形式被加载到zygote进程的空间中,并由libart.so负责虚拟机的功能,完成对oat文件的解析,方法的查找和调用,并负责垃圾回收。
  2. runtime可以实现在部分未被编译的方法和已被编译的方法之前的交互调用,为此runtime提供了诸如artInterpreterToInterpreterBridge、artInterpreterToCompiledCodeBridge之类的函数进行衔接。
  3. 所有的Java方法在编译为arm指令后都符合一定的标准。由于是在runtime中运行的,所有的R0寄存器代表着一个隐含的参数,指令当前的ArtMethod对象,R1-R3传递前几个参数(包括this),多余的参数依靠堆栈传递。
  4. 系统的启动类(在环境变量BOOTCLASSPATH中指定)被翻译为boot.oat,boot.art包含了其加载后的类对象,启动时以直接被载入进程空间中。
  5. 同一个dex文件中的方法,载入的时候会被直接解析到ArtMethod对象的dex_cache_resolved_methods_成员中,直接通过R0寄存器寻址。而系统的API主要是通过找到代表包含API的对象Object实例中的Class域,然后在其中的函数表中查找解决的;Class实例的初始化,是在载入每个oat文件解析类信息时建立的。
  6. 一些关键的系统调用,如分配对象等,是有libart.so来提供的,并且与平台有相关性,存放在每个Thread对象的quick_entrypoints_域中。
  7. dex2oat在两个时间被执行,一是apk安装的时候,二是在调用DexClassLoader动态载入dex文件的时候。
  8. 具体的说编译的目标指令为Thumb-2指令集,支持16位指令和32位指令的混合执行。
  9. 在编译boot.art和boot.oat文件时,不需要其他的支持,但是在编译其他的dex文件时需要在虚拟机环境中载入上述文件。编译执行的过程也需要虚拟机环境的支持,只不过是用于编译而非执行,这样可以保证编译的目标文件是在虚拟机环境中的一个完整的映像而不会出现寻址错误等。
  10. 整个编译过程基本上是依靠dex2oat来加载CompilerDriver,然后逐个方法来进行编译。将每个方法划分BasicBlock,绘制MIRGraph(控制流图),逐个翻译为以dalvikbtecode的SSA形式为基础的MIR,然后将MIR解析为LIR,最后翻译为Thumb-2指令,最后统一写入一个ELF文件即oat文件。
相关文章
  • 也来看看Android的ART运行时 也来看看Android的ART运行时

    之前因为需要,研究了一下ART的相关源码,也做了一些记录与总结,现在重新整理了一下与大家共同讨论和交流一下. 0x00 概述 ART是Android平台上的新一代运行时,用来代替dalvik.它主要采用了AOT的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行,节省了运行时间,提高了效率,但是在一定程度上使得安装的时间变长,空间占用变大. 从

  • ART运行时Semi-Space(SS)跟Generational Semi-Space(GSS)GC执行过程分析 ART运行时Semi-Space(SS)跟Generational Semi-Space(GSS)GC执行过程分析

    ART运行时Semi-Space(SS)和Generational Semi-Space(GSS)GC执行过程分析 Semi-Space(SS)GC和Generational Semi-Space(GSS)GC是ART运行时引进的两个Compacting GC.它们的共同特点是都具有一个From Space和一个To Space.在GC执行期间,在From Space分配的还存活的对象会被依次拷贝到To Space中,这样就可以达到消除内存碎片的目的.本文就将SS GC和GSS GC的执行过程分

  • ART运行时Compacting GC替新创建对象分配内存的过程分析 ART运行时Compacting GC替新创建对象分配内存的过程分析

    ART运行时Compacting GC为新创建对象分配内存的过程分析 在引进Compacting GC后,ART运行时优化了堆内存分配过程.最显著特点是为每个ART运行时线程增加局部分配缓冲区(Thead Local Allocation Buffer)和在OOM前进行一次同构空间压缩(Homogeneous Space Compact).前者可提高堆内存分配效率,后者可解决内存碎片问题.本文就对ART运行时引进Compacting GC后的堆内存分配过程进行分析. 老罗的新浪微博:http:/

  • ART运行时Compacting GC简要介绍和学习计划 ART运行时Compacting GC简要介绍和学习计划

    在前面一个系列文章中,我们学习了Android 4.4 ART的Mark-Sweep(MS)GC.到了Android 5.0,ART增加了对Compacting GC的支持,包括Semi-Space(SS).Generational Semi-Space(GSS)和Mark-Compac (MC)三种.本文就对Android 5.0 ART的Compacting GC进行简要介绍以及制定学习计划. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 总体来说,

  • ART运行时Foreground GC跟Background GC切换过程分析 ART运行时Foreground GC跟Background GC切换过程分析

    ART运行时Foreground GC和Background GC切换过程分析 通过前面一系列文章的学习,我们知道了ART运行时既支持Mark-Sweep GC,又支持Compacting GC.其中,Mark-Sweep GC执行效率更高,但是存在内存碎片问题:而Compacting GC执行效率较低,但是不存在内存碎片问题.ART运行时通过引入Foreground GC和Background GC的概念来对这两种GC进行扬长避短.本文就详细分析它们的执行过程以及切换过程. 老罗的新浪微博:h

  • ART运行时Compacting GC简明介绍和学习计划 ART运行时Compacting GC简明介绍和学习计划

    ART运行时Compacting GC简要介绍和学习计划 在前面一个系列文章中,我们学习了Android 4.4 ART的Mark-Sweep(MS)GC.到了Android 5.0,ART增加了对Compacting GC的支持,包括Semi-Space(SS).Generational Semi-Space(GSS)和Mark-Compac (MC)三种.本文就对Android 5.0 ART的Compacting GC进行简要介绍以及制定学习计划. 老罗的新浪微博:http://weibo

  • ART运行时Foreground GC和Background GC切换过程分析 ART运行时Foreground GC和Background GC切换过程分析

    通过前面一系列文章的学习,我们知道了ART运行时既支持Mark-Sweep GC,又支持Compacting GC.其中,Mark-Sweep GC执行效率更高,但是存在内存碎片问题;而Compacting GC执行效率较低,但是不存在内存碎片问题.ART运行时通过引入Foreground GC和Background GC的概念来对这两种GC进行扬长避短.本文就详细分析它们的执行过程以及切换过程. 我们都有提到了ART运行时的Foreground GC和Background GC.它们是在ART

  • ART运行时Compacting GC简要介绍 ART运行时Compacting GC简要介绍

    在前面一个系列文章中,我们学习了Android4.4 ART的Mark-Sweep(MS)GC.到了Android 5.0,ART增加了对Compacting GC的支持,包括Semi-Space(SS).Generational Semi-Space(GSS)和Mark-Compact (MC)三种.本文对Android 5.0 ART的Compacting GC进行简要介绍以及制定学习计划. 总体来说,Compacting GC和Mark-Sweep GC各有优劣.所谓Compacting

  • ART运行时Compacting GC堆创建过程分析 ART运行时Compacting GC堆创建过程分析

    引进了Compacting GC之后,ART运行时的堆空间结构就发生了变化.这是由于Compacting GC和Mark-Sweep GC的算法不同,要求底层的堆具有不同的空间结构.同时,即使是原来的Mark-Sweep GC,由于需要支持新的同构空间压缩特性(Homogeneous Space Compact),也使得它们要具有与原来不一样的堆空间结构.本文就对这些堆空间创建过程进行详细的分析. 从前面ART运行时Java堆创建过程分析一文可以知道,在没有Compacting GC之前,Mar

  • Android Studio编译运行时 Local path doesn't exist Android Studio编译运行时 Local path doesn't exist

    Android Studio编译运行时 Local path doesn't exist. 最近把Android Studio 从0.2.x更新到了0.3.1,因为Android Studio 0.3.0开始还是更早的版本开始推荐使用Gradle 1.8以上的版本,于是我便Gradle官网更新了Gradle http://www.gradle.org/downloads 一直没有什么问题,可是当我重新编译工程然后运行的时候,发现Android Studio的Console开始报错 引用 Wait

  • ART运行时Compacting GC为新创建对象分配内存的过程分析 ART运行时Compacting GC为新创建对象分配内存的过程分析

    在引进Compacting GC后,ART运行时优化了堆内存分配过程.最显著特点是为每个ART运行时线程增加局部分配缓冲区(Thead Local Allocation Buffer)和在OOM前进行一次同构空间压缩(Homogeneous Space Compact).前者可提高堆内存分配效率,后者可解决内存碎片问题.本文就对ART运行时引进Compacting GC后的堆内存分配过程进行分析. 从接口层面上看,除了提供常规的对象分配接口AllocObject,ART运行时的堆还提供了一个专门

  • ART运行时Mark-Compact( MC)GC执行过程分析 ART运行时Mark-Compact( MC)GC执行过程分析

    除了Semi-Space(SS)GC和Generational Semi-Space(GSS)GC,ART运行时还引入了第三种Compacting GC:Mark-Compact(MC)GC.这三种GC虽然都是Compacting GC,不过它们的实现方式却有很大不同.SS GC和GSS GC需两个Space来压缩内存,而MC GC只需一个Space来压缩内存.本文就详细分析MC GC的执行过程. 从前面ART运行时Semi-Space(SS)和Generational Semi-Space(G

  • ART运行时Semi-Space(SS)和Generational Semi-Space(GSS)GC执行过程分析 ART运行时Semi-Space(SS)和Generational Semi-Space(GSS)GC执行过程分析

    Semi-Space(SS)GC和Generational Semi-Space(GSS)GC是ART运行时引进的两个Compacting GC.它们的共同特点是都具有一个From Space和一个To Space.在GC执行期间,在From Space分配的还存活的对象会被依次拷贝到To Space中,这样就可以达到消除内存碎片的目的.本文就将SS GC和GSS GC的执行过程分析进行详细分析. 与SS GC相比,GSS GC还多了一个Promote Space.当一个对象是在上一次GC之前分

  • ART运行时Java堆创建过程分析 ART运行时Java堆创建过程分析

    与Dalvik虚拟机一样,ART运行时内部也有一个Java堆,用来分配Java对象.当这些Java对象不再被使用时,ART运行时需要回收它们占用的内存.在前面一文中,我们简要介绍了ART运行时的垃圾收集机制,从中了解到ART运行时内部使用的Java堆是由四种Space以及各种辅助数据结构共同描述的.为了后面可以更好地分析ART运行时的垃圾收集机制,本文就对它内部使用的Java堆的创建过程进行分析. ART运行时创建Java堆的过程,实际上就是创建在图1涉及到的各种数据结构: 图1 ART运行时堆

  • 近距离端详Android ART运行时库 近距离端详Android ART运行时库

    Table of Contents 在最新的Google I/O大会上,Google 发布了关于Android上最新的运行时库的情况.这就是Android RunTime (ART). ART 将会取代Dalvik虚拟机,成为Android平台上Java代码的执行工具.虽然自从Android KitKat,就有了一些关于ART的消息,但是基本都是一些新闻性质的,缺乏具体技术细节方面的介绍.本文尝试综合目前已有的各种消息,以及最新放出的Android L 预览版本的ROM的情况,对ART运行时库做

  • Android运行时ART加载类和方法的过程分析 Android运行时ART加载类和方法的过程分析

    在前一篇文章中,我们通过分析OAT文件的加载过程,认识了OAT文件的格式,其中包含了原始的DEX文件.既然ART运行时执行的都是翻译DEX字节码后得到的本地机器指令了,为什么还需要在OAT文件中包含DEX文件,并且将它加载到内存去呢?这是因为ART运行时提供了Java虚拟机接口,而要实现Java虚拟机接口不得不依赖于DEX文件.本文就通过分析ART运行时加载类及其方法的过程来理解DEX文件的作用. 老罗的新浪微博: http://weibo.com/shengyangluo ,欢迎关注! 在前面

  • Android运行时ART简要介绍和学习计划 Android运行时ART简要介绍和学习计划

    Android在4.4就已推出新运行时ART,准备替代用了有些时日的Dalvik.不过当时尚属测试版,主角仍是Dalvik. 直到今年的Google I/O大会,ART才正式取代Dalvik.这个消息在科技界引起不小轰动,也吸引不少技术人员对它的"技术分析".可惜这些"技术分析"不过是引用了官方的数据和图表而已.这一系列文章将对ART进行真正的技术分析.老规矩,分析前先进行简要介绍和制定学习计划. 老罗的新浪微博: http://weibo.com/shengyan

  • Android运行时ART执行类方法的过程分析 Android运行时ART执行类方法的过程分析

    在前面一篇文章中,我们分析了ART运行时加载类以及查找其方法的过程.一旦找到了目标类方法,我们就可以获得它的DEX字节码或者本地机器指令,这样就可以对它进行执行了.在ART运行时中,类方法的执行方式有两种.一种是像Dalvik虚拟机一样,将其DEX字节码交给解释器执行:另一种则是直接将其本地机器指令交给CPU执行.在本文中,我们就将通过分析ART运行时执行类方法的过程来理解ART运行时的运行原理. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 我们先来看

  • Android运行时ART加载OAT文件的过程分析 Android运行时ART加载OAT文件的过程分析

    在前面一文中,我们介绍了Android运行时ART,它的核心是OAT文件.OAT文件是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容.这使得我们无需重新编译原有的APK就可以让它正常地在ART里面运行,也就是我们不需要改变原来的APK编程接口.本文我们通过OAT文件的加载过程分析OAT文件的结构,为后面分析ART的工作原理打基础. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! OAT文件

  • 关于Android模拟器运行时,鼠标放到虚拟键盘下报错的解决方法

    关于Android模拟器运行时,鼠标放到虚拟键盘上报错的解决办法 我的情况是Android Virtual Device Manager里的AVD Name里有数字,改成首字母大写,全字母的就好了.

最新文章
  • 转换率 转化率 这 2 个词语 有区别吗 ,在什么场合下有区别呢

    转换率 转化率 这 2 个词语 有区别吗 ,在什么场合下有区别呢 谢谢 --cut-- liangguan5在2016-05-09 20:19:38回答到: 转换率: A 变成 B 的比值 转化率: A 变成了 A 的比值 liangguan5在2016-05-09 20:19:38回答到: sorry typo 转换率: A 变成 B 的比值 转化率: A 变成了 A+ 的比值 dikcen在2016-05-09 20:19:38回答到: 转化率应该较自身的比值,例如△A / A 起始的量,或

  • 孕妇不喜欢喝牛奶怎么补钙 孕妇不喜欢喝牛奶怎么补钙

    孕妇不喜欢喝牛奶怎么补钙 本站阅读配图 一:酸奶.低乳糖牛奶解决喝奶后腹胀不适 很多人喝奶后会感觉腹部不适.腹胀或腹泻,这都是由于牛奶中含有乳糖的缘故.牛奶中的乳糖进入小肠后,需要乳糖酶来消化分解之后才能吸收.然而,有相当一部分人肠道中缺少乳糖酶,所以不能消化吸收乳糖.未消化吸收的乳糖进入大肠后,被大肠内的细菌发酵利用,产生气体(腹胀.不适),同时还使肠腔内水分增多(稀便或腹泻).这种现象医学上称之为"乳糖不耐受症". 乳糖不耐受的人其实仍然可以喝奶--喝不含乳糖的奶制品.酸奶是最常见

  • js和as的稳定传值问题解决

    最近在实现flash的播放音乐的功能,这就涉及到js和as交互的问题,因为要实现动态改变音乐文件的功能,可是如何判定呢? 但是在实现js传值给flash时,flash在获取值存在几率性,有时可以获得到,有时有获取不到.后来发现,其实是由于<object ...></object>所对应的flash没有加载完,在js就开始调用flash中的 ExternalInterface.addCallback()所开放的方法,导致flash还没获取到值时就开始播放音乐,此时,当然播放不了.为了

  • 笑一笑 经典搞笑句子

    张飞在公交车上手机被偷,小偷尚未下车,于是大喊一声:给我把手机下!吓得车上有多人晕倒,十几个人将手机扔到地上,张飞最后捡了一个最好的回家了. 一日.一农民进京坐公交.听到好播报员讲"乘客们注意啦,下站停靠公主坟,有下车的的朋友,请?闯迪岷竺乓贫?"只听那农民大叫:"我要上坟,我要上坟.差点坐过站啦."车厢里一片唏嘘---- 一次公交途中,很多人从后门挤上来,门都关不上了,也没有人投钱,司机当时真的很生气,大声吼道,你们再不投钱都给我滚下去,再不下去,我给你们滚下去!

  • 各位老师,请问一个海量数据对比的有关问题

    各位老师,请教一个海量数据对比的问题 两张表 CIP_quitcon JIN_quitcon 分别是1200万条数据 表结构如下 CREATE TABLE quitcon ( { D21 退保金表 } classcode char(6), sex char(1), pass smallint, age smallint, yearnum smallint, yqauct decimal(10,2), oqauct decimal(10,2), ydact decimal(10,2), odauc

  • java 并发(一) 理解Thread.join()

    java 并发(1) 理解Thread.join() thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B. t.join(); //当前线程中加入线程t,当t执行完成后,当前线程才继续进行. t.join(1000); //等待 t 线程,等待时间是1000毫秒,无论t是否执行完,主线程1000s后都将会执行. 应用一 : 实现多个线程的串行化调度.代码如下: pa

  • 整数因子分解有关问题

    整数因子分解问题 Description 大于1的正整数n可以分解为:n=x1*x2*-*xm. 例如,当n=12 时,共有8 种不同的分解式: 12=12: 12=6*2: 12=4*3: 12=3*4: 12=3*2*2: 12=2*6: 12=2*3*2: 12=2*2*3. 对于给定的正整数n,计算n共有多少种不同的分解式. Input 输入数据只有一行,有1个正整数n (1≤n≤2000000000). Output 将计算出的不同的分解式数输出. Sample Input 12 Sa

  • 网络受限下,使用Nexus要解决的两个有关问题 网络受限下,使用Nexus要解决的两个有关问题

    网络受限下,使用Nexus要解决的两个问题 在网络受限的情况下,使用nexus总会遇到这么两个问题,让你头疼. 我头疼过了,为了不让大家头疼,把解决方案放在这里,供大家参考. 问题一.背景: 由于网络原因,Nexus无法更新远程仓库的索引. 解决方案1: 1.首先在能连接远程仓库的机器上更新索引,建议使用eclipse maven插件,开启full Index,然后更新索引,需要等一段时间. 2.找到这个目录:本地repository\.cache\m2e\1.4.0\26522e0d83a42

  • 用FindComponent查找Edit控件出错,该怎么处理

    用FindComponent查找Edit控件出错 源程序 procedure TFormRounting.RountingJiShu( arr1: array of Byte; acount: Integer); var i:Integer; C:TComponent; begin for i:=1 to acount do begin C:= FormRounting.FindComponent('edt'+inttostr(i)); if C<>nil then begin arr1[i-

  • 数据部门-基本概念 数据部门-基本概念

    数据机构-基本概念 百度版: 数据结构是计算机存储.组织数据的方式.数据结构是指相互之间存在一种或多种特定关系的数据元素的集合.通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率.数据结构往往同高效的检索算法和索引技术有关. 数据结构里面的一些重要概念: 1.逻辑结构与物理结构 1.1逻辑结构(重点) 指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.(百度版) 主要有几种关系: 集合关系: 集合关系的数学图示: 线性

热门推荐
  • 出全新 iPad Air 2(金/16GB/Cellular 机型)及 Bose SoundTrue 贴耳式耳机(薄荷绿) 出全新 iPad Air 2(金/16GB/Cellular 机型)及 Bose SoundTrue 贴耳式耳机(薄荷绿) iPad Air 2 WLAN + Cellular 16GB 机型 - 金色(购于美国 Bestbuy ) 3688 元(顺丰保价到付) Bose SoundTrue 贴耳式耳机-薄荷绿(购于美国 Bestbuy ) 产品官网 http://www.bose.cn/product.aspx?cid=945#945 799 元(顺丰保价到付) 联系 mrhaoji#gmail.com 长沙 V2EXer 可联系面提 --cut-- cheny95在2016-05-09 19:14:40回答到:
  • 世界无烟日是几月几日 世界无烟日是几月几日 2015世界无烟日是几月几日 星期几 世界卫生组织1987年11月建议将每年的4月7日定为"世界无烟日(World No-Tobacco Day)",并于1988年开始执行.自1989年起,世界无烟日改为每年的5月31日.2015世界无烟日是5月31日,星期日.
  • 孕妇风疹病毒阳性 孕妇风疹病毒阳性 孕妇出现风疹病毒呈现阳性特征的时候需要注意自己很可能已经感染上了风疹病毒,要注意自己的身上是否出现了风疹的现象,及时的清洁自己的身体,特别是腹部和外阴,防止风疹病毒的滋生.因为孕妇在怀孕期间不可以随便使用药物,否则很可能给腹中的胎儿造成影响.因此必要的一些中成药或者是外用防止风疹的药膏是可以适当的使用的,起到治疗作用. 方法: 1.当孕妇被诊断出风疹病毒阳性的指标时,需要马上对肚子里的孩子进行检验,必要的时候可以采取母婴隔离的手段,缓解患者可能会出现的症状,同时不会对胎儿造成传染,影响胎儿的正常
  • photoshop设计一张太空场景海报效果制作教程 photoshop设计一张太空场景海报效果制作教程 今天小编在这里就来给photoshop的这一款软件的使用者们来说下设计一张太空场景海报效果的制作教程,各位想知道具体制作方法的使用者,那么下面就快来看一看小编下面的教程吧. 给各位photoshop软件的使用者们来详细的解析分享一下设计一张太空场景海报效果的制作教程. 教程分享: 通过这个教程你可以学习到如何从零开始创建一个太空背景,以简单的方式创造一颗行星,并使用图层混合模式来衔接这些太空元素,同时也可以学习到调整色彩以及对比度.建立灯光效果.增加景深等知识. 好了,以上的信息就是小编给各位p