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

我們在平時開發過程中,一定定義過無數個千奇百怪的類,但是大家有想過,一個 Java 文件中的 Class,在虛擬機中的真實形態是什麼麼?

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

視頻鏈接
Android虛擬機Java對象初始化原理,Class之間的關係
鏈接: https://pan.baidu.com/s/1AisNpQGWVrCHWaN-gkJwjg 提取碼: d4gf

更多面試內容,面試專題,flutter視頻 全套,音視頻從0到高手開發。
關注GitHub:https://github.com/xiangjiana/Android-MS
免費獲取面試PDF合集
免費提供簡歷修改建議,獲取大廠面試PDF和視頻教程

預備知識

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

看完本文可以達到什麼程度

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

閱讀前準備工作

  1. 下載 ART 源碼 作爲參照

文章概覽

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

對於如何在 Java 代碼中定義一個類,我們一定非常熟悉了,代碼如下:

  class MInterface {
      void imethod() {
      }
   }

  class Parent {
   }

  class Child extends Parent implements MInterface {
   }

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

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

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

 

下面我們就看看這個 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 中,我們可以理解爲所有子類複寫的方法都是虛方法。

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

三、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

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

加載靜態變量
加載實例變量
加載方法,分爲虛方法和非虛方法
由於這裏代碼比較長,我們分段來看。

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 設置方法入口,即從其他方法如何跳轉到此方法進行執行。這裏也分爲了幾種情況:

  • 如果此方法已經通過 OAT 編譯成了本地機器指令,那麼這裏會將入口設置爲跳轉到本地機器指令執行
  • 如果是靜態方法,設置跳板方法,此時不會具體指定方法如何執行,後面會在 ClassLinker::InitializeClass 裏被 ClassLinker::FixupStaticTrampolines 替換掉
  • 如果是 Native 方法,入口設置爲跳轉到 JNI 動態連接的方法中
  • 如果是解釋模式,入口設置爲跳轉到解釋器中
    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 中類的全貌。

視頻鏈接
Android虛擬機Java對象初始化原理,Class之間的關係
鏈接: https://pan.baidu.com/s/1AisNpQGWVrCHWaN-gkJwjg 提取碼: d4gf

更多面試內容,面試專題,flutter視頻 全套,音視頻從0到高手開發。
關注GitHub:https://github.com/xiangjiana/Android-MS
免費獲取面試PDF合集
免費提供簡歷修改建議,獲取大廠面試PDF和視頻教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章