在初略了解了ClassLinker之后,我们下一步先了解一些相关的类或者结构体。ArtField在ART虚拟机底层中用来表示类的成员变量的;ArtMethod则是用来表示类的成员方法;DexCache和Dex文件有关,用于换成Dex文件的信息;Class则是在ART虚拟机中表示类的信息。在开始前需要先了解相关类之间的关系,如下图(摘自《深入理解ART虚拟机一书》)
其次,还需要再简单了解下Dex文件的格式,Dex文件的排列内容可以参考如下:
Dex文件的各个节简单解释一下:
- header:DEX 文件头,记录了一些当前文件的信息以及其他数据结构在文件中的偏移量
- string_ids:字符串的偏移量
- type_ids:成员变量、成员方法等的类型信息的偏移量
- proto_ids:方法声明的偏移量
- field_ids:字段信息的偏移量
- method_ids:方法信息(所在类、方法声明、方法名)的偏移量
- class_def:类信息的偏移量
- data:数据区
- link_data:静态链路数据区
上述的字段表示的都是一些偏移量数组,并未存储真是的数据,在解析Dex文件的时候是通过这些偏移量到data区进行查找。
了解一下ArtField
下面只列举一下相关内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class ArtField final { public: // 获取成员变量对应的ClassLoader ObjPtr<mirror::ClassLoader> GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_); // 这里获取类中成员变量的访问控制类型,public、protect还是private uint32_t GetAccessFlags() REQUIRES_SHARED(Locks::mutator_lock_) { if (kIsDebugBuild) { GetAccessFlagsDCheck(); } return access_flags_; } ............. // 如果成员变量是Int类型,可以通过下面的getter和setter方法进行设置或者获取成员变量所代表的值 // 当然,如果是boolean、short等类型,也会有相关的getter或者setter方法,篇幅文件就不列出来 int32_t GetInt(ObjPtr<mirror::Object> object) REQUIRES_SHARED(Locks::mutator_lock_); template<bool kTransactionActive> void SetInt(ObjPtr<mirror::Object> object, int32_t i) REQUIRES_SHARED(Locks::mutator_lock_); // 如果成员变量不是基本类型,这通过Object表示 ObjPtr<mirror::Object> GetObject(ObjPtr<mirror::Object> object) REQUIRES_SHARED(Locks::mutator_lock_); template<bool kTransactionActive> void SetObject(ObjPtr<mirror::Object> object, ObjPtr<mirror::Object> l) REQUIRES_SHARED(Locks::mutator_lock_); // 获取成员变量所在的Dex文件信息 const DexFile* GetDexFile() REQUIRES_SHARED(Locks::mutator_lock_); private: ................... // 用来表示定义该成员变量的类 GcRoot<mirror::Class> declaring_class_; // 用来表示成员变量的访问标记 uint32_t access_flags_ = 0; // 该成员变量在Dex文件field_ids数组中的索引值 uint32_t field_dex_idx_ = 0; // 实例内或类的静态字段中字段的偏移量,和class管理成员变量有关 uint32_t offset_ = 0; |
如上所示,一个ArtField对象代表类中的一个成员变量。比如,一个Java类A中有一个名为a的long型变量。那么,在ART虚拟机中就有一个ArtField对象来表示这个a。但是从上面的定义中我们可以了解到,ArtField中是没有表示这个long型变量的成员变量的,而且对应的setter或者getter方法获取或者设置的都是Ptr指针。所以ArtField对应仅仅只是用来表示一个Java类的成员变量,但是它自己并不提供内存空间来存储这个Java成员变量的内容。
了解一下ArtMethod
下面列举了一部分ArtMethod类的内容,省略了大部分内容,为了篇幅,省略号也不留了,省略掉省略号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
class ArtMethod final { public: ................. // 事实上返回的是 ArtMethod 结构体的指针地址,所以可以强制类型转换成 ArtMethod 结构体指针 static ArtMethod* FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject jlr_method) REQUIRES_SHARED(Locks::mutator_lock_); // 返回定义该Method所在类 template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> ALWAYS_INLINE ObjPtr<mirror::Class> GetDeclaringClass() REQUIRES_SHARED(Locks::mutator_lock_); // 获取该类的访问标记 uint32_t GetAccessFlags() const { return access_flags_.load(std::memory_order_relaxed); } // 设置访问标记,注释中有提及,仅当确保没有并发性时才应调用此版本,因此需要确保并发性 void SetAccessFlags(uint32_t new_access_flags) REQUIRES_SHARED(Locks::mutator_lock_) { access_flags_.store(new_access_flags, std::memory_order_relaxed); } // 返回该Method在Dex文件中method_ids中的偏移量 static constexpr MemberOffset DexMethodIndexOffset() { return MemberOffset(OFFSETOF_MEMBER(ArtMethod, dex_method_index_)); } // 返回该method在ART虚拟机vtable中的偏移量 static constexpr MemberOffset MethodIndexOffset() { return MemberOffset(OFFSETOF_MEMBER(ArtMethod, method_index_)); } // 从DexCache中,通过类型索引中查找对应的Class ObjPtr<mirror::Class> LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_); // 查找对应的Class并设置到DexCache中去 ObjPtr<mirror::Class> ResolveClassFromTypeIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_); // 下面几个方法是在方法加载、链接的时候会根据代码的不同进行设置 // SetEntryPointFromQuickCompiledCode设置所执行的方法的位置 // GetEntryPointFromQuickCompiledCode用于返回执行的类型:OatMethod or DexMethod or NativeMethod const void* GetEntryPointFromQuickCompiledCode() const { return GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize); } void SetEntryPointFromQuickCompiledCode(const void* entry_point_from_quick_compiled_code) REQUIRES_SHARED(Locks::mutator_lock_) { SetEntryPointFromQuickCompiledCodePtrSize(entry_point_from_quick_compiled_code, kRuntimePointerSize); } // 运行Dex中未优化过的代码时,ART虚拟机会记录相关的热点函数保存在Profile文件中,这里时获取Profile文件相关内容 ProfilingInfo* GetProfilingInfo(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_) { if (UNLIKELY(IsNative() || IsProxyMethod() || !IsInvokable())) { return nullptr; } return reinterpret_cast<ProfilingInfo*>(GetDataPtrSize(pointer_size)); } // 这里用于设置方法运行时的Profile信息 ALWAYS_INLINE void SetProfilingInfo(ProfilingInfo* info) REQUIRES_SHARED(Locks::mutator_lock_) { SetDataPtrSize(info, kRuntimePointerSize); } ............... protected: // 表示本方法在哪个类中进行声明 GcRoot<mirror::Class> declaring_class_; // 表示方法的访问标志 std::atomic<std::uint32_t> access_flags_; //表示dex_code_item的偏移量 uint32_t dex_code_item_offset_; // 表示该方法的偏移量 uint32_t dex_method_index_; // 与ArtField的field_index_类似,下面这个成员变量和Class类如何管理它的成员函数有关。 uint16_t method_index_; // 热度。函数每被调用一次,该值就递增1。一旦超过某个阈值,该函数可能就会被编译成本地方法,已加快执行速度。 union { uint16_t hotness_count_; uint16_t imt_index_; }; struct PtrSizedFields { // 根据方法类型的不同,data_会指向不同的内容 void* data_; // 其中entry_point_from_quick_compiled_code_保存的是方法的入口地址,在类加载的LinkCode()函数中设置入口地址 void* entry_point_from_quick_compiled_code_; } ptr_sized_fields_; .................... } |
一个ArtMethod代表一个Java类中的成员方法。对一个方法而言,它的入口函数地址是最核心的信息。
了解一下DexCache
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class MANAGED DexCache final : public Object { .............. // 前面涉及的函数方法略过,这里我们先关注private成员变量 private: // DexCache对应的ClassLoader HeapReference<ClassLoader> class_loader_; // dex文件对应的路径 HeapReference<String> location_; // 实际为DexFile*,指向所关联的那个Dex文件 uint64_t dex_file_; // 具体为GcRoot<mirror::String*>*类型,指向GcRoot<mirror::String*>数组 uint64_t preresolved_strings_; // 具体为GcRoot<CallSite>*类型,指向GcRoot<CallSite>数组 uint64_t resolved_call_sites_; // 实际为ArtField **,指向ArtField*数组,成员的数据类型为ArtField*。该数组存储了一个Dex文件中定义的所有类的成员变量。 // 另外,只有那些经过解析后得到的ArtField对象才会存到这个数组里。该字段和Dex文件里的field_ids数组有关。 uint64_t resolved_fields_; // 和resolved_fields类型 uint64_t resolved_method_types_; // 实际为ArtMethod**,指向ArtMethod*数组,成员类型为ArtMethod*。该数组存储了一个Dex文件中定义的所有类的成员函数。 // 另外,只有那些经过解析后得到的ArtMethod对象才会存到这个数组里。该字段和Dex文件里的method_ids数组有关。 uint64_t resolved_methods_; // 实际为GcRoot<Class>*,指向GcRoot<Class>数组,成员的数据类型为GcRoot<Class>,本质上就是mirror::Class*。 // 它存储该dex文件里使用的数据类型信息数组。该字段和Dex文件里的type_ids数组有关 uint64_t resolved_types_; // 实际为GcRoot<String>*,指向GcRoot<String>数组,包括该dex文件里使用的字符串信息数组。 // 注意GcRoot<String>本质上就是mirror::String*。该字段和dex文件的string_ids数组有关 uint64_t strings_; // 下面的变量代表上述各个数组的长度 uint32_t num_preresolved_strings_; uint32_t num_resolved_call_sites_; uint32_t num_resolved_fields_; uint32_t num_resolved_method_types_; uint32_t num_resolved_methods_; uint32_t num_resolved_types_; uint32_t num_strings_; } |
- Dex文件按照自己的格式存储将不同的信息分别存储,比如type_ids、string_ids、field_ids和method_ids等
- Dex文件里大部分都是借助symbol reference来间接获取目标信息。相反,DexCache则是直接包含最终的目标信息。比如,dex文件的type_ids数组里保存的实际上是该dex文件里用到的或者自定义的数据类型所对应名称在string_ids数组里的索引,而DexCache resolved_types包含的则是一个
GcRoot<Class>*
数组,它存储的内容直接指向dex文件里用到的或自定义数据类型所定义的Class对象。由于从symbol reference到最终的信息需要经过一个解析的过程,所以上面DexCache的那几个成员变量命名中都有resolved_的前缀。
了解一下Class
Art虚拟机中使用的class类,我们和DexCache一样,只关注其中的成员变量,至于对成员变量的操作方法,我们后面遇到再看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
class MANAGED Class final : public Object { ............... private: // 加载本类的ClassLoader对象,如果为空,则是bootstrap system loader HeapReference<ClassLoader> class_loader_; // 下面的这个成员变量对数组类才有意义,用于表示数组元素的类型。比如,对String[][][]类而言 // componet_type_代表String[][]。本章后文介绍数组类的时候还会讨论它 HeapReference<Class> component_type_; // 该类缓存在哪个DexCache对象中。注意,有些累是由虚拟机直接创建的,而不是从Dex文件里读取的。 // 比如基础数据类型。这种情况下dex_cache_取值为空 HeapReference<DexCache> dex_cache_; // 表示额外的类数据,并且该字段是延迟分配的 HeapReference<ClassExt> ext_data_; // IfTable从ObjectArray<Object>派生,所以它实际上是一个数组容器。为什么不直接使用它的父类ObjectArray<Object>呢? // 根据ART虚拟机的设计,IfTable中的一个索引位置其实包含两项内容,第一项是该类所实现的接口类的Class对象,第二项 // 则是和第一项接口类有关的接口函数信息。这里先使用伪代码来描述IfTable中索引x对应的内容: // 第一项内容:具体位置为iftable_内部数组[x+0],元素类型为Class*,代表某个接口类 // 第二项内容:具体位置为iftable_内部数组[x+1],元素类型为PointArray*。 // 另外对于类A而言,它的iftable_所包含的信息来自如下三个方面: // a. 类A自己所实现的接口类 // b. 类A从父类(direct superclass)那里获取的信息 // c. 类A从接口父类(direct super interface)那里获取的信息 HeapReference<IfTable> iftable_; // 本类的类名 HeapReference<String> name_; // 代表父类。如果本类代表Object或者基础数据类型,则该成员变量为空 HeapReference<Class> super_class_; // Virtual Method Table。它指向一个PointArray数组,元素类型为ArtMethod* // 这个vtable_的内容非常丰富,下面的章节再详细介绍 HeapReference<PointerArray> vtable_; // 指向DexCache的strings_成员变量实际为LengthPrefixedArray<ArtField>,代表本类声明的 // 非静态成员变量。注意这个LengthPrefixedArray元素类型是ArtField,不是ArtField* uint64_t ifields_; // 下面三个成员变量需要配合使用。其中,methods_实际是LengthPrefixedArray<ArtMethod>, // 代表该类自己定义的成员函数。它包括类里定义的virtual和direct的成员函数,也包括从接口类中 // 继承得到的默认函数以及所谓的Miranda函数,methods_中元素的排列如下: // [0, virtual_methods_offset_) 为本类包含的direct成员函数 // [virtual_methods_offset_, copied_methods_offset_) 为本类包含的virtual成员函数 // [copied_methods_offset_, ...) 为剩下的诸如Miranda函数内容 uint64_t methods_; uint16_t copied_methods_offset_; uint16_t virtual_methods_offset_; // 同ifields_类型,只不过保存的是本类的静态成员变量 uint64_t sfields_; // 访问标记,低16位为虚拟机内部定义使用 uint32_t access_flags_; // 类标记,用于加快成员的查找过程 uint32_t class_flags_; // 当分配一个类对象时,用于说明这个类对象所需的内存大小 uint32_t class_size_; // 代表执行该类初始化函数的线程ID pid_t clinit_thread_id_; static_assert(sizeof(pid_t) == sizeof(int32_t), "java.lang.Class.clinitThreadId size check"); // 本类在dex文件中class_defs数组对应元素的索引 int32_t dex_class_def_idx_; // 本类在dex文件中type_ids数组的索引 int32_t dex_type_idx_; // 下面两个成员变量表示本类定义的引用类型和静态成员变量的个数 uint32_t num_reference_instance_fields_; uint32_t num_reference_static_fields_; // 该类的实例所占据的内存大小。也就是我们在Java层new一个该类的实例时,这个实例 // 所需要的内存大小 uint32_t object_size_; uint32_t object_size_alloc_fast_path_; // 低16为是 Primitive::Type 的枚举值. 高16位另作他用 uint32_t primitive_type_; // 下面这个变量指向一个内存地址,该地址中存储的是一个位图, // 它和Class中用于表示引用类型的非静态成员变量(ifields)的信息有关 uint32_t reference_instance_offsets_; // 类的状态 uint32_t status_; // 特别注意,下面三个注释的成员变量,在代码类定义中虽然被注释了,但是在 // 实际的Class对象内存空间中可能包含对应的内容,笔者称之为Class的隐含成员变量。 // 他们的取值情况在后面遇到时我们再详细分析 // Embedded Imtable,内嵌的Interface Method Table,是一个固定大小的数组。数组元素 // 的类型为ImTableEntry,但代码中并不存在这样的数据类型。实际上,下面这个隐含成员变量 // 的声明可用 ArtMethod* embeded_imtable_[0]来表示 // ImTableEntry embedded_imtable_[0]; // Embedded Vtable,内嵌的Virtual Table,是一个非固定大小的数组。数组元素为VTableEntry, // 但代码中也不存在这样的数据类型。和上面的embedded_imtable类似,它的声明也可以用 // ArtMethod* embedded_vtable_[0]来表示 // VTableEntry embedded_vtable_[0]; // 下面这个数组存储Class中的静态成员变量信息 // uint32_t fields_[0]; ART_FRIEND_TEST(DexCacheTest, TestResolvedFieldAccess); // For ResolvedFieldAccessTest friend struct art::ClassOffsets; // for verifying offset information friend class Object; // For VisitReferences friend class linker::ImageWriter; // For SetStatusInternal DISALLOW_IMPLICIT_CONSTRUCTORS(Class); }; |
上述Class的成员变量比较多,如下几个成员变量尤其值得读者关注。我们先介绍他们的情况
- iftable_:保存了该类所有直接实现或者间接实现的接口信息。直接实现是指该类自己implements的某个接口。间接实现是指它的继承关系上有某个祖父类implements了某个接口。另外,一条接口信息包含两个部分,第一个部分是接口类所对应的Class对象,第二部分则是该接口类中的接口方法
- vtable_:和iftable_类似,它保存了该类所有直接定义或间接定义的virtual方法信息。比如,Object类中有耳熟能详的wait、notify、toString等的11个virtual方法。所以任意一个派生类中都包含这11个方法
- methods_:methods_指包含本类直接定义的direct方法、virtual方法和那些拷贝过来的诸如Miranda这样的方法。一般而言,vtable_包含的内容要远多于methods_
- embedded_imtable_、embedded_vtable_和fields_为隐含成员变量。其中,前两个变量只能实例化的类中才存在。实例化是指该类在Java层的对应类可以通过new来创建一个对象。举个反例,基础数据类、抽象类、接口类就属于不能实例化的类
OK,这篇就简单了解下ArtField、ArtMethod、DexCache和Class类相关的内容。