脫了馬甲我也認識你: 聊聊 Android 中類的真實形態

這是 ZY 第 19 篇原創技術文章java

咱們在平時開發過程當中,必定定義過無數個千奇百怪的類,可是你們有想過,一個 Java 文件中的 Class,在虛擬機中的真實形態是什麼麼?android

這篇文章就帶你們探討一下在 Android ART 裏,類的真實形態,以及類加載的過程c++

本文基於 ART-8.0.0_r1 分支代碼進行分析git

預備知識

  1. 瞭解 Java 基本開發
  2. 瞭解 ClassLoader 基本使用

看完本文能夠達到什麼程度

  1. 瞭解 Android ART 中類的存在形式
  2. 瞭解 Android ART 中類加載的過程

閱讀前準備工做

  1. 下載 ART 源碼 做爲參照

文章概覽

summary

1、在 Java 中如何定義一個類

對於如何在 Java 代碼中定義一個類,咱們必定很是熟悉了,代碼以下:github

interface MInterface {
    void imethod() {
    }
}

class Parent {
}

class Child extends Parent implements MInterface {
}
複製代碼

2、ART 中如何表示一個 Java 類

那麼對於一個 Java 類,在 ART 中是如何表示的呢?
在 ART 中,也定義了一個 Class 類,用來表示 Java 世界中的類。
固然,這個類是 c++ 定義的,畢竟 ART 就是 c++ 實現的。   bootstrap

下面這張圖展現了 ART 中類的重要部分。
數組

class

下面咱們就看看這個 Class 的具體定義:緩存

2.1 類的定義

// 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 實例上。

2.2 類方法的定義

在 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_;
}
複製代碼

2.3 類屬性的定義

在 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;
}
複製代碼

3、ART 中加載類的過程

3.1 類加載的本質

在 Java 中定義好一個類以後,還須要經過 ClassLoader 進行加載。
咱們常常會說到類加載,可是類加載的本質是什麼呢?
在咱們上面瞭解了一個 Java 類在 ART 中的真實形態之後,咱們就比較容易理解類加載的本質了。
咱們都知道,Java 文件編譯完成的產物是 .class 文件,在 Android 中是 .dex 文件,類加載的本質就是解析 .class / .dex 文件,並根據對應的信息生成 ArtField,ArtMethod,最後生成 Class 實例。
再簡單點來講,類加載的本質就是根據 .dex 文件內容建立 Class 實例。

3.2 ART 中類加載的入口 -- ClassLinker#DefineClass

在 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 的代碼裏咱們能夠看到,加載分爲幾個步驟:

  1. 建立類實例
  2. 設置 Class 訪問修飾符,ClassLoader 等一些屬性
  3. 加載類成員 LoadClass
  4. 加載父類和接口 LoadSuperAndInterfaces
  5. 鏈接 LinkClass

下面咱們主要看下後面加載類成員,加載父類,鏈接這三個步驟。

3.3 加載類成員 -- LoadClass

加載類成員這一過程,主要有下面幾個步驟:

  1. 加載靜態變量
  2. 加載實例變量
  3. 加載方法,分爲虛方法和非虛方法 因爲這裏代碼比較長,咱們分段來看。
3.3.1 加載靜態變量
// 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_ 變量。

3.3.2 加載實例變量

加載實例變量和加載靜態變量是相似的,這裏不作過多的解讀了。

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);
  }
}
複製代碼
3.3.3 加載方法
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 設置方法入口,即從其餘方法如何跳轉到此方法進行執行。這裏也分爲了幾種狀況:

  1. 若是此方法已經經過 OAT 編譯成了本地機器指令,那麼這裏會將入口設置爲跳轉到本地機器指令執行
  2. 若是是靜態方法,設置跳板方法,此時不會具體指定方法如何執行,後面會在 ClassLinker::InitializeClass 裏被 ClassLinker::FixupStaticTrampolines 替換掉
  3. 若是是 Native 方法,入口設置爲跳轉到 JNI 動態鏈接的方法中
  4. 若是是解釋模式,入口設置爲跳轉到解釋器中
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());
  }
  // ...
}
複製代碼

這就是解析方法的主要過程,關於方法的調用,其實還比較複雜,若是你們感興趣,後面能夠再專門說說。

3.4 加載父類和接口 -- LoadSuperAndInterfaces

自身類成員加載完成後,就去加載父類。加載父類調用的是 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 來的,因而加載父類的流程又回到了咱們本小結開頭。
就這樣遞歸加載下去,直到父類所有加載完成,也就標識着類自身也加載完成了。

3.5 鏈接 -- LinkClass

以後就是 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;
}
複製代碼

從主要代碼中能夠看到,主要有四個步驟:

  1. LinkSuperClass
  2. LinkMethods
  3. LinkInstanceFields
  4. LinkStaticFields
3.5.1 LinkSuperClass

這裏主要是對父類權限作了一下檢查,包括是不是 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;
}
複製代碼
3.5.2 LinkMethods

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_,這裏具體的代碼很長,咱們暫且不分析(這裏具體流程對於理解本文主旨其實影響不大),有兩個重要的過程是:

  1. 首先會拷貝父類的 vtable 到當前類的 vtable
  2. 若是類中覆蓋了父類的抽象方法,就在 vtable 中替換掉父類的方法

經過上面兩個過程,咱們能夠知道,vtable 中保存的就是真正方法的實現,也就是 Java 中多態的實現原理。

3.5.3 LinkInstanceFields & LinkStaticFields

這裏的兩個方法最終都調用了 LinkFields 方法裏作了兩件事情:

  1. 爲了對齊內存,對 fields 進行排序
  2. 計算 Class 大小

其中 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 中類的全貌。

class

參考資料

www.zhihu.com/question/48…
blog.csdn.net/guoguodaern…

關於我

ZYLAB 專一高質量原創,把代碼寫成詩

歡迎關注下面帳號,獲取更新:
微信搜索公衆號: ZYLAB
Github
知乎
掘金

相關文章
相關標籤/搜索