【這是 ZY 第 19 篇原創技術文章】java
咱們在平時開發過程當中,必定定義過無數個千奇百怪的類,可是你們有想過,一個 Java 文件中的 Class,在虛擬機中的真實形態是什麼麼?android
這篇文章就帶你們探討一下在 Android ART 裏,類的真實形態,以及類加載的過程。c++
本文基於 ART-8.0.0_r1 分支代碼進行分析git
對於如何在 Java 代碼中定義一個類,咱們必定很是熟悉了,代碼以下:github
interface MInterface {
void imethod() {
}
}
class Parent {
}
class Child extends Parent implements MInterface {
}
複製代碼
那麼對於一個 Java 類,在 ART 中是如何表示的呢?
在 ART 中,也定義了一個 Class 類,用來表示 Java 世界中的類。
固然,這個類是 c++ 定義的,畢竟 ART 就是 c++ 實現的。 bootstrap
下面這張圖展現了 ART 中類的重要部分。
數組
下面咱們就看看這個 Class 的具體定義:緩存
// C++ mirror of java.lang.Class
class MANAGED Class FINAL : public Object {
private:
// 指向定義 Class 的 ClassLoader,若是爲 null,說明是 bootstrap system loader
HeapReference<ClassLoader> class_loader_;
// 對於數組類型有用,保存了數組的原始類型,好比 對於 String[],這裏指向的是 String
// 對非數組類型,值爲 null
HeapReference<Class> component_type_;
// 指向 DexCache,若是是運行時生成的 Class,值爲 null
HeapReference<DexCache> dex_cache_;
HeapReference<ClassExt> ext_data_;
// interface table,接口方法表,IfTable 中保存了接口類指針和方法表指針
HeapReference<IfTable> iftable_;
// Descriptor for the class such as "java.lang.Class" or "[C". Lazily initialized by ComputeName
// 類描述符 eg: java.lang.Class 或者 [C
HeapReference<String> name_;
// 父類,若是是 java.lang.Object 值爲 null
HeapReference<Class> super_class_;
// 虛方法表,"invoke-virtual" 指令會用到,用來保存父類虛方法以及自身虛方法
HeapReference<PointerArray> vtable_;
// 保存類屬性,只保存自身屬性
uint64_t ifields_;
// 指向 ArtMethod 數組,保存了全部的方法,包括私有方法,靜態方法,final 方法,虛方法和繼承的方法
uint64_t methods_;
// 保存靜態屬性
uint64_t sfields_;
// 訪問修飾符
uint32_t access_flags_;
uint32_t class_flags_;
// 類實例大小,GC 時使用
uint32_t class_size_;
// 線程 id,類加載時加鎖使用
pid_t clinit_thread_id_;
// ClassDex 在 DEX 文件中的 index
int32_t dex_class_def_idx_;
// DEX 文件中的類型 id
int32_t dex_type_idx_;
// 實例屬性數量
uint32_t num_reference_instance_fields_;
// 靜態變量數量
uint32_t num_reference_static_fields_;
// 對象大小,GC 時使用
uint32_t object_size_;
uint32_t object_size_alloc_fast_path_;
uint32_t primitive_type_;
// ifields 的偏移量
uint32_t reference_instance_offsets_;
// 類初始化狀態
Status status_;
// methods_ 中第一個從接口中複製的虛方法的偏移
uint16_t copied_methods_offset_;
// methods_ 中第一個自身定義的虛方法的偏移
uint16_t virtual_methods_offset_;
// java.lang.Class
static GcRoot<Class> java_lang_Class_;
};
複製代碼
上面的類就是 Java 類在 ART 中的真實形態,各個屬性在上面作了註釋。
這裏對幾個比較重要的屬性再作一下解釋。微信
和 Java 類方法有關的兩個屬性是 iftable_,vtable_
和 methods_
。
其中 iftable_
保存的是接口中的方法,vtable_
保存的是虛方法,methods_
保存的是全部方法。
什麼是虛方法呢?虛方法實際上是 C++ 中的概念,在 C++ 中,被 virtual
關鍵字修飾的方法就是虛方法。
而在 Java 中,咱們能夠理解爲全部子類複寫的方法都是虛方法。cookie
和 Java 類屬性有關的兩個屬性是 ifields_
和 sfields_
。分別保存的是類的實例屬性和靜態屬性。
從上面的咱們能夠看到,Java 類的屬性就都保存在 ART 中定義的 Class 裏了。
其中方法最終會指向 ArtMethod 實例上,屬性,最終會指向 ArtField 實例上。
在 ART 中,一個 Java 的類方法是用 ArtMethod 實例來表示的。
ArtMethod 結構以下:
class ArtMethod FINAL {
protected:
// 定義此方法的類
GcRoot<mirror::Class> declaring_class_;
// 訪問修飾符
std::atomic<std::uint32_t> access_flags_;
// 方法 code 在 dex 中的偏移
uint32_t dex_code_item_offset_;
// 方法在 dex 中的 index
uint32_t dex_method_index_;
// 方法 index,對於虛方法,指的是 vtable 中的 index,對於接口方法,指的是 ifTable 中的 index
uint16_t method_index_;
// 方法的熱度計數,Jit 會根據此變量決定是否將方法進行編譯
uint16_t hotness_count_;
struct PtrSizedFields {
ArtMethod** dex_cache_resolved_methods_;
void* data_;
// 方法的入口
void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
}
複製代碼
在 ART 中,一個 Java 類屬性是用 ArtField 實例來表示的。
ArtField 結構以下:
class ArtField FINAL {
private:
// 定義此屬性的類
GcRoot<mirror::Class> declaring_class_;
// 訪問修飾符
uint32_t access_flags_ = 0;
// 變量在 dex 中的 id
uint32_t field_dex_idx_ = 0;
// 此變量在類或者類實例中的偏移
uint32_t offset_ = 0;
}
複製代碼
在 Java 中定義好一個類以後,還須要經過 ClassLoader 進行加載。
咱們常常會說到類加載,可是類加載的本質是什麼呢?
在咱們上面瞭解了一個 Java 類在 ART 中的真實形態之後,咱們就比較容易理解類加載的本質了。
咱們都知道,Java 文件編譯完成的產物是 .class 文件,在 Android 中是 .dex 文件,類加載的本質就是解析 .class / .dex 文件,並根據對應的信息生成 ArtField,ArtMethod,最後生成 Class 實例。
再簡單點來講,類加載的本質就是根據 .dex 文件內容建立 Class 實例。
在 Android 中,常見的兩個 ClassLoader 就是 PathClassLoader 和 DexClassLoader,都是繼承了 BaseDexClassLoader,咱們就從 BaseDexClassLoader#findClass
開始看一下整個加載的流程。
// BaseDexClassLoader#findClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
// ...
return c;
}
// DexPathList#findClass
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
// ...
return null;
}
// Element#findCLass
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
複製代碼
從上面的代碼來看,BaseDexClassLoader#findClass
一路調用,調用到 DexFile#loadClassBinaryName
,咱們再繼續往下看。
// DexFile
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
複製代碼
在 DexFile 裏,最終調用到 defineClassNative
方法去加載 Class,對應到 JNI 中的方法是 DexFile_defineClassNative
,位於 runtime/native/dalvik_system_DexFile.cc 文件中。
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader, jobject cookie, jobject dexFile) {
// 調用
for (auto& dex_file : dex_files) {
ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
descriptor.c_str(),
hash,
class_loader,
*dex_file,
*dex_class_def);
}
}
複製代碼
而在 defineClassNative
中,又是調用 ClassLinker#DefineClass
去加載類的。
因此咱們能夠說,ClassLinker#DefineClass
就是 ART 中類加載的入口。
入口已經出現,咱們就進去探索一番,看看類加載的時候,是如何建立 Class 實例的~
DefineClass 自己代碼比較多,咱們這裏把代碼簡化一下,看其主要流程。
mirror::Class* ClassLinker::DefineClass(Thread* self,
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def) {
auto klass = hs.NewHandle<mirror::Class>(nullptr);
// 一些經常使用的,而且類大小能夠肯定的,會提早構造好對應的 Class,因此這裏直接使用
if (UNLIKELY(!init_done_)) {
// finish up init of hand crafted class_roots_
if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
klass.Assign(GetClassRoot(kJavaLangObject));
} else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
klass.Assign(GetClassRoot(kJavaLangClass));
} else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
klass.Assign(GetClassRoot(kJavaLangString));
} else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {
klass.Assign(GetClassRoot(kJavaLangRefReference));
} else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
klass.Assign(GetClassRoot(kJavaLangDexCache));
} else if (strcmp(descriptor, "Ldalvik/system/ClassExt;") == 0) {
klass.Assign(GetClassRoot(kDalvikSystemClassExt));
}
}
if (klass == nullptr) {
// 建立其餘類實例
klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
}
// 設置對應的 DEX 緩存
klass->SetDexCache(dex_cache);
// 設置 Class 的一些屬性,包括 ClassLoader,訪問修飾符,Class 在 DEX 中對應的 index 等等
SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get());
// 把 Class 插入 ClassLoader 的 class_table 中作一個緩存
ObjPtr<mirror::Class> existing = InsertClass(descriptor, klass.Get(), hash);
// 加載類屬性
LoadClass(self, *new_dex_file, *new_class_def, klass);
// 加載父類
if (!LoadSuperAndInterfaces(klass, *new_dex_file)) {
// 加載失敗的處理
}
if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) {
// 鏈接失敗的處理
}
// ...
return h_new_class.Get();
}
複製代碼
從上面 DefineClass 的代碼裏咱們能夠看到,加載分爲幾個步驟:
下面咱們主要看下後面加載類成員,加載父類,鏈接這三個步驟。
加載類成員這一過程,主要有下面幾個步驟:
// class_linker.cc
void ClassLinker::LoadClassMembers(Thread* self,
const DexFile& dex_file,
const uint8_t* class_data,
Handle<mirror::Class> klass) {
{
// Load static fields.
// 獲取 DEX 文件中的變量迭代器
ClassDataItemIterator it(dex_file, class_data);
LengthPrefixedArray<ArtField>* sfields = AllocArtFieldArray(self,
allocator,
it.NumStaticFields());
// ...
// 遍歷靜態變量
for (; it.HasNextStaticField(); it.Next()) {
// ...
LoadField(it, klass, &sfields->At(num_sfields));
}
// ...
klass->SetSFieldsPtr(sfields);
}
}
// 加載變量,設置變量 Class 以及訪問修飾符
void ClassLinker::LoadField(const ClassDataItemIterator& it,
Handle<mirror::Class> klass,
ArtField* dst) {
const uint32_t field_idx = it.GetMemberIndex();
dst->SetDexFieldIndex(field_idx);
dst->SetDeclaringClass(klass.Get());
dst->SetAccessFlags(it.GetFieldAccessFlags());
}
複製代碼
加載靜態變量時,取出 DEX 文件中對應的 Class 數據,遍歷其中的靜態變量,設置給 Class#sfield_
變量。
加載實例變量和加載靜態變量是相似的,這裏不作過多的解讀了。
void ClassLinker::LoadClassMembers(Thread* self,
const DexFile& dex_file,
const uint8_t* class_data,
Handle<mirror::Class> klass) {
{
// Load instance fields.
LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self,
allocator,
it.NumInstanceFields());
for (; it.HasNextInstanceField(); it.Next()) {
LoadField(it, klass, &ifields->At(num_ifields));
}
// ...
klass->SetIFieldsPtr(ifields);
}
}
複製代碼
void ClassLinker::LoadClassMembers(Thread* self,
const DexFile& dex_file,
const uint8_t* class_data,
Handle<mirror::Class> klass) {
{
for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) {
ArtMethod* method = klass->GetDirectMethodUnchecked(i, image_pointer_size_);
LoadMethod(dex_file, it, klass, method);
LinkCode(this, method, oat_class_ptr, class_def_method_index);
// ...
}
for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
ArtMethod* method = klass->GetVirtualMethodUnchecked(i, image_pointer_size_);
LoadMethod(dex_file, it, klass, method);
LinkCode(this, method, oat_class_ptr, class_def_method_index);
// ...
}
}
}
複製代碼
加載方法時分爲兩個步驟,LoadMethod 和 LinkCode。
void ClassLinker::LoadMethod(const DexFile& dex_file,
const ClassDataItemIterator& it,
Handle<mirror::Class> klass,
ArtMethod* dst) {
// ...
dst->SetDexMethodIndex(dex_method_idx);
dst->SetDeclaringClass(klass.Get());
dst->SetCodeItemOffset(it.GetMethodCodeItemOffset());
dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods(), image_pointer_size_);
uint32_t access_flags = it.GetMethodAccessFlags();
// ...
dst->SetAccessFlags(access_flags);
}
複製代碼
LoadMethod 主要是給 ArtMethod 設置訪問修飾符等屬性。
LinkCode 這一步驟,能夠理解爲是給 ArtMethod 設置方法入口,即從其餘方法如何跳轉到此方法進行執行。這裏也分爲了幾種狀況:
ClassLinker::InitializeClass
裏被 ClassLinker::FixupStaticTrampolines
替換掉static void LinkCode(ClassLinker* class_linker, ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) {
if (oat_class != nullptr) {
// 判斷方法是否已經被 OAT
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index);
oat_method.LinkMethod(method);
}
// Install entry point from interpreter.
const void* quick_code = method->GetEntryPointFromQuickCompiledCode();
bool enter_interpreter = class_linker->ShouldUseInterpreterEntrypoint(method, quick_code);
if (method->IsStatic() && !method->IsConstructor()) {
// 對於靜態方法,後面會在 ClassLinker::InitializeClass 裏被 ClassLinker::FixupStaticTrampolines 替換掉
method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub());
} else if (quick_code == nullptr && method->IsNative()) {
// Native 方法跳轉到 JNI
method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
} else if (enter_interpreter) {
// 解釋模式,跳轉到解釋器
method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
}
// ...
}
複製代碼
這就是解析方法的主要過程,關於方法的調用,其實還比較複雜,若是你們感興趣,後面能夠再專門說說。
自身類成員加載完成後,就去加載父類。加載父類調用的是 LoadSuperAndInterfaces
,主要代碼以下:
bool ClassLinker::LoadSuperAndInterfaces(Handle<mirror::Class> klass, const DexFile& dex_file) {
// 加載父類
ObjPtr<mirror::Class> super_class = ResolveType(dex_file, super_class_idx, klass.Get());
// 檢查父類可見性
if (!klass->CanAccess(super_class)) {
// ...
}
// 設置父類
klass->SetSuperClass(super_class);
// 加載接口
const DexFile::TypeList* interfaces = dex_file.GetInterfacesList(class_def);
for (size_t i = 0; i < interfaces->Size(); i++) {
ObjPtr<mirror::Class> interface = ResolveType(dex_file, idx, klass.Get());
// ...
// 檢查接口可見性
if (!klass->CanAccess(interface)) {
}
}
// 此時說明類已經加載完畢了
mirror::Class::SetStatus(klass, mirror::Class::kStatusLoaded, nullptr);
}
複製代碼
加載父類和接口都是經過 ResolveType 來的,ResolveType 中又是調用了 ClassLinker#FindClass
-> ClassLinker#DefineClass
來的,因而加載父類的流程又回到了咱們本小結開頭。
就這樣遞歸加載下去,直到父類所有加載完成,也就標識着類自身也加載完成了。
以後就是 LinkClass,這裏步驟比較清晰,咱們先看一下主要代碼:
bool ClassLinker::LinkClass(Thread* self,
const char* descriptor,
Handle<mirror::Class> klass,
Handle<mirror::ObjectArray<mirror::Class>> interfaces,
MutableHandle<mirror::Class>* h_new_class_out) {
if (!LinkSuperClass(klass)) {
return false;
}
// ...
if (!LinkMethods(self, klass, interfaces, &new_conflict, imt_data)) {
return false;
}
if (!LinkInstanceFields(self, klass)) {
return false;
}
size_t class_size;
if (!LinkStaticFields(self, klass, &class_size)) {
return false;
}
// ...
return true;
}
複製代碼
從主要代碼中能夠看到,主要有四個步驟:
這裏主要是對父類權限作了一下檢查,包括是不是 final,是否對子類可見(父類爲 public 或者同包名),以及繼承父類一些屬性(包括是否有 finalize 方法,ClassFlags 等等)。
bool ClassLinker::LinkSuperClass(Handle<mirror::Class> klass) {
ObjPtr<mirror::Class> super = klass->GetSuperClass();
//
// Verify
if (super->IsFinal() || super->IsInterface()) {
}
if (!klass->CanAccess(super)) {
}
if (super->IsFinalizable()) {
klass->SetFinalizable();
}
if (super->IsClassLoaderClass()) {
klass->SetClassLoaderClass();
}
uint32_t reference_flags = (super->GetClassFlags() & mirror::kClassFlagReference);
klass->SetClassFlags(klass->GetClassFlags() | reference_flags);
return true;
}
複製代碼
LinkMethods 主要作的事情是填充 vtable 和 itable。主要經過 SetupInterfaceLookupTable,LinkVirtualMethods,LinkInterfaceMethods
三個方法來進行的:
bool ClassLinker::LinkMethods(Thread* self,
Handle<mirror::Class> klass,
Handle<mirror::ObjectArray<mirror::Class>> interfaces,
bool* out_new_conflict,
ArtMethod** out_imt) {
// ...
return SetupInterfaceLookupTable(self, klass, interfaces)
&& LinkVirtualMethods(self, klass, /*out*/ &default_translations)
&& LinkInterfaceMethods(self, klass, default_translations, out_new_conflict, out_imt);
}
複製代碼
SetupInterfaceLookupTable 用來填充 iftable_
,就是上面說到保存接口的地方。iftable_
對應的是 IfTable 類。IfTable 類結構以下:
class MANAGED IfTable FINAL : public ObjectArray<Object> {
enum {
// Points to the interface class.
kInterface = 0,
// Method pointers into the vtable, allow fast map from interface method index to concrete
// instance method.
kMethodArray = 1,
kMax = 2,
};
}
複製代碼
其中 kInterface
指向 Interface 的 Class 對象,kMethodArray
指向的是 vtable,經過此變量能夠方便的找到接口方法的實現。
LinkVirtualMethods 和 LinkInterfaceMethods 會填充 vtable_,這裏具體的代碼很長,咱們暫且不分析(這裏具體流程對於理解本文主旨其實影響不大),有兩個重要的過程是:
經過上面兩個過程,咱們能夠知道,vtable 中保存的就是真正方法的實現,也就是 Java 中多態的實現原理。
這裏的兩個方法最終都調用了 LinkFields 方法裏作了兩件事情:
其中 fields 排序規則以下:
引用類型 -> long (64-bit) -> double (64-bit) -> int (32-bit) -> float (32-bit) -> char (16-bit) -> short (16-bit) -> boolean (8-bit) -> byte (8-bit)
經過上面的分析,咱們知道了一個 Java 類在 Android ART 中的真實形態,也對 ART 中類加載的過程作了一些簡單的分析。
其實在寫這篇文章的時候,裏面有一些知識點也會有些疑問,若是你們有任何想法,歡迎討論~
最後用文章開始的圖總結一下,回顧一下 ART 中類的全貌。
www.zhihu.com/question/48…
blog.csdn.net/guoguodaern…
ZYLAB 專一高質量原創,把代碼寫成詩
歡迎關注下面帳號,獲取更新:
微信搜索公衆號: ZYLAB
Github
知乎
掘金