天天看點

Android知識鞏固—類加載機制

Java中的類加載機制

類加載器簡單來說是用來加載 Java 類到 Java 虛拟機中的。Java 虛拟機使用 Java 類的方式如下:Java 源程式(.java 檔案)在經過 Java 編譯器編譯之後就被轉換成 Java 位元組代碼(.class 檔案)。類加載器負責讀取 Java 位元組代碼,并轉換成 java.lang.Class類的一個執行個體。

Java類加載的流程

JVM類加載分為5個過程:加載,連結,準備,解析,初始化,使用,解除安裝。

  1. 加載: 加載主要是将.class檔案(并不一定是.class。可以是ZIP包,網絡中擷取)中的二進制位元組流讀入到JVM中。

    (1)通過類的全限定名擷取該類的二進制位元組流。

    (2)靜态存儲結構轉化為方法區的運作時資料結構。

    (3)在記憶體中生成一個該類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口。

  2. 連結:連結就是将Java類的二進制代碼合并到java的運作狀态中的過程。

    (1)驗證:確定加載的類符合JVM規範與安全。

    (2)準備:為static變量在方法區中配置設定空間,設定變量的初始值。

    (3)解析:虛拟機将常量池的符号引用轉變成直接引用。

  3. 初始化:初始化階段是執行類構造器()方法。在類構造器方法中,它将由編譯器自動收集類中的所有類變量的指派動作(準備階段的a正是被指派a)和靜态變量與靜态語句塊static{}合并。
  4. 使用:正常使用。
  5. 解除安裝:GC把無用對象從記憶體中解除安裝。

Java類加載的時機

一個類真正被加載的時機是在建立對象的時候,才會去執行以上過程,加載類。

主動引用(發生類初始化過程)

  1. new一個對象。
  2. 調用類的靜态成員(除了final常量)和靜态方法。
  3. 通過反射對類進行調用。
  4. 虛拟機啟動,main方法所在類被提前初始化。
  5. 初始化一個類,如果其父類沒有初始化,則先初始化父類。

被動引用(不會發生類的初始化)

  1. 當通路一個靜态變量時,隻有真正聲明這個變量的類才會初始化。(子類調用父類的靜态變量,隻有父類初始化,子類不初始化)。
  2. 通過數組定義類引用,不會觸發此類的初始化。
  3. final變量不會觸發此類的初始化,因為在編譯階段就存儲在常量池中。

類加載器

  1. 啟動類加載器(Bootstrap ClassLoader):最頂層的類加載器,負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數指定路徑中的,且被虛拟機認可(按檔案名識别,如rt.jar)的類。
  2. 擴充類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。
  3. 應用程式類加載器(Application ClassLoader):也叫做系統類加載器,可以通過getSystemClassLoader()擷取,負責加載使用者路徑(classpath)上的類庫。如果沒有自定義類加載器,一般這個就是預設的類加載器。

除此之外,還有自定義的類加載器,它們之間的層次關系被稱為類加載器的雙親委派模型。該模型要求除了頂層的啟動類加載器外,其餘的類加載器都應該有自己的父類加載器,而這種父子關系一般通過組合(Composition)關系來實作,而不是通過繼承(Inheritance)。

源碼如下:

  1. 首先檢查類是否被加載,沒有則調用父類加載器的loadClass()方法。
  2. 若父類加載器為空,則預設使用啟動類加載器作為父加載器。
  3. 若父類加載失敗,抛出ClassNotFoundException 異常後,再調用自己的findClass() 方法。

緩存 -> 父類加載器 -> 沒有父類 -> 啟動類加載器 -> 自己的 findClass() 方法

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先從緩存查找該class對象,找到就不用重新加載
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      //如果找不到,則委托給父類加載器去加載
                      c = parent.loadClass(name, false);
                  } else {
                  //如果沒有父類,則委托給啟動加載器去加載
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }
              if (c == null) {
                  // 如果都沒有找到,則通過自定義實作的findClass去查找并加載
                  c = findClass(name);
                  // this is the defining class loader; record the stats
                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  sun.misc.PerfCounter.getFindClasses().increment();
              }
          }
          if (resolve) {//是否需要在加載時進行解析
              resolveClass(c);
          }
          return c;
      }
  }
           

測試代碼:

public class Test {
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader());
System.out.println(Test.class.getClassLoader().getParent());
System.out.println(Test.class.getClassLoader().getParent().getParent());
           

執行結果:

[email protected]
[email protected]
null
           

AppClassLoader 和 ExtClassLoader 由 Java 編寫并且都是 java.lang.ClassLoader 的子類,而 BootstarapClassLoader 并非由 Java 實作而是由C++ 實作,是以列印結果為null。

雙親委派模型過程為,當某個特定的類加載器在接到加載類的請求時,首先将加載任務委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功傳回;隻有父類加載器無法完成此加載任務時,才自己去加載。

使用雙親委派模型的好處在于Java類随着它的類加載器一起具備了一種帶有優先級的層次關系。例如類java.lang.Object,它存在在rt.jar中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的Bootstrap ClassLoader進行加載,是以Object類在程式的各種類加載器環境中都是同一個類。相反,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果使用者編寫了一個java.lang.Object的同名類并放在ClassPath中,那系統中将會出現多個不同的Object類,程式将混亂。是以,如果開發者嘗試編寫一個與rt.jar類庫中重名的Java類,可以正常編譯,但是永遠無法被加載運作。

Android中的類加載機制

Android中的虛拟機無論是dvm還是art都隻能識别dex檔案。是以Java中的ClassLoader在Android中不适用。Android中的java.lang.ClassLoader這個類也不同于Java中的java.lang.ClassLoader。Android中的ClassLoader類型也可分為系統ClassLoader和自定義ClassLoader。

  1. BootClassLoader,Android系統啟動時會使用BootClassLoader來預加載常用類,與Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代碼實作,而是由Java實作的。BootClassLoader是ClassLoader的一個内部類。
  2. PathClassLoader,全名是dalvik/system.PathClassLoader,可以加載已經安裝的Apk,也就是/data/app/package 下的apk檔案,也可以加載/vendor/lib, /system/lib下的nativeLibrary。
  3. DexClassLoader,全名是dalvik/system.DexClassLoader,可以加載一個未安裝的apk檔案。

在Activity中列印目前的ClassLoader:

public class Test2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);
        Log.i("Test2Activity", Test2Activity.class.getClassLoader()+"");
        Log.i("Test2Activity", Test2Activity.class.getClassLoader().getParent()+"");
        Log.i("Test2Activity", Test2Activity.class.getClassLoader().getParent().getParent()+"");
    }
}
           
2019-01-20 20:25:33.453 5841-5841/? I/Test2Activity: dalvik.system.PathClassLoader[DexPathList[[zip file "/system/framework/org.apache.http.legacy.boot.jar", zip file "/data/app/com.testingapp-_K7ZDNqjamLgiVLWYpv9cg==/base.apk"],nativeLibraryDirectories=[/data/app/com.testingapp-_K7ZDNqjamLgiVLWYpv9cg==/lib/arm64, /system/lib64]]]
2019-01-20 20:25:33.454 5841-5841/? I/Test2Activity: [email protected]
2019-01-20 20:25:33.454 5841-5841/? I/Test2Activity: null
           

從列印的結果也可以證明:App系統類加載器是PathClassLoader,而BootClassLoader是其parent類加載器

Android知識鞏固—類加載機制

Android中的類加載器是BootClassLoader、PathClassLoader、DexClassLoader,其中BootClassLoader是虛拟機加載系統類需要用到的,PathClassLoader是App加載自身dex檔案中的類用到的,DexClassLoader可以加載直接或間接包含dex檔案的檔案,如APK等。

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,它的一個DexPathList類型的成員變量pathList很重要。DexPathList中有一個Element類型的數組dexElements,這個數組中存放了包含dex檔案(對應的是DexFile)的元素。BaseDexClassLoader加載一個類,最後調用的是DexFile的方法進行加載的。

參考文章:

類加載機制系列2——深入了解Android中的類加載器

繼續閱讀