Android虛擬機框架:類加載機制

關於做者html

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。java

文章目錄android

這篇文章咱們來聊一聊關於Android虛擬機的那些事,固然這裏咱們並不須要去講解關於虛擬機的底層細節,所講的東西都是你們日常在開發中常常用的。例如類的加載機制、資源加載機制、APK打包流程、APK安裝流程 以及Apk啓動流程等。講解這些知識是爲了後續的文章《大型Android項目的工程化實踐:插件化》、《大型Android項目的工程化實踐:熱更新》、《大型Android項目的工程化實踐:模塊化》等系列的文章作一個 原理鋪墊。git

好了,讓咱們開始吧~😁程序員

一 類文件基本結構

Class文件是一組以8位字節爲基礎的單位的二進制流,各個數據項按嚴格的順序緊密的排列在Class文件中,中間沒有任何間隔。github

這麼說有點抽象,咱們先來舉一個簡單的小例子。🤞數組

public class TestClass {

    public int sum(int a, int b) {
        return a + b;
    }
}
複製代碼

編譯生成Class文件,而後使用hexdump命令查看Class文件裏的內容。緩存

javac TestClass.java
hexdump TestClass.class
複製代碼

Class文件內容以下所示:bash

Classfile /Users/guoxiaoxing/Github-app/android-open-source-project-analysis/demo/src/main/java/com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass.class
  Last modified 2018-1-23; size 333 bytes
  MD5 checksum 72ae3ff578aa0f97b9351522005ec274
  Compiled from "TestClass.java"
public class com.guoxiaoxing.android.framework.demo.native_framwork.vm.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass.m:I
   #3 = Class              #17            // com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass
  #18 = Utf8               java/lang/Object
{
  public com.guoxiaoxing.android.framework.demo.native_framwork.vm.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 15: 0
}
SourceFile: "TestClass.java"

複製代碼

Class文件十六機制內容以下所示:cookie

注:筆者用的二進制查看軟件是iHex,能夠去AppStore下載,Windows用戶可使用WinHex。

這是一份十六進制表示的二進制流,每一個位排列緊密,都有其對應的含義,具體說來,以下所示:

注:下列表中四個段分別爲 類型、名稱、說明、數量

  • u4 magic 識別Class文件格式,具體值爲0xCAFEBABE 1
  • u2 minor_version Class文件格式副版本號 1
  • u2 major_version Class文件格式主版本號 1
  • u2 constant_pool_count 常數表項個數 1
  • cp_info constant_pool 常數表,又稱變長符號表 constant_pool_count-1
  • u2 access_flags Class的聲明中使用的修改符掩碼 1
  • u2 this_class 常數表索引,索引內保存類名或接口名 1
  • u2 super_class 常數表索引,索引內保存父類名 1
  • u2 interfaces_count 超接口個數 1
  • u2 interfaces 常數表索引,各超接口名稱 interfaces_count
  • u2 fields_count 類的域個數 1
  • field_info fields 域數據,包括屬性名稱索引 fields_count
  • u2 methods_count 方法個數 1
  • method_info methods 方法數據,包括方法名稱索引 methods_count
  • u2 attributes_count 類附加屬性個數 1
  • attribute_info attributes 類附加屬性數據,包括源文件名稱等 attributs_count

咱們能夠看着在上面這張表中有相似u二、attribute_info這樣的類型,事實上Class文件採用一種相似於C語言結構體的僞結構struct來存儲數據,這種結構有兩種數據類型:

  • 無符號數:基本數據類型,例如u1表明1個字節,u2表明2個字節,u4表明2個字節,u8表明8個字節。
  • 表:由多個無符號數或者其餘表做爲數據項而構成的複合數據結構,用於描述有層次關係的複合數據結構,通常以"_info"結尾。

咱們分別來看看上述的各個字段的具體含義已經對應數值。

注:這一塊的內容可能有點枯燥,可是它是咱們後續學習類加載機制,Android打包機制,以及學習插件化、熱更新框架的基礎,因此須要掌握。 可是也不必都記住每一個段的含義,你只須要有個總體性的認識便可,後續若是忘了具體的內容,能夠再回來查閱。😁

1.1 魔數

具體含義

魔數:1-4字節,用來肯定這個文件是否爲一個能被虛擬機接受的Class文件,它的值爲0xCAFEBABE。

對應數值

ca fe ba be

1.2 版本號

具體含義

版本號:5-6字節是次版本號,7-8字節是主版本號

對應數值

5-6字節是次版本號0x0000(即0),7-8字節是主版本號0x0034(即52).

JDK版本號與數值的對應關係以下所示:

  • JDK 1.8 = 52
  • JDK 1.7 = 51
  • JDK 1.6 = 50
  • JDK 1.5 = 49
  • JDK 1.4 = 48
  • JDK 1.3 = 47
  • JDK 1.2 = 46
  • JDK 1.1 = 45

1.3 常量池計數/常量池

具體含義

常量池計數:常量池中常量的數量不是固定的,所以常量池入口處會放置一項u2類型的數據,表明常量池容器計數。注意容器計數從1開始,索引爲0表明不引用任何一個 常量池的項目。

對應數值

9-10字節是常量池容器計數0x0013(即19)。說明常量池裏有18個常量,從1-18.

這是咱們上面用javap分析的字節碼文件裏的常量池裏常量的個數是一直的。

舉個常量池裏的常量的例子🤞

它的常量值以下所示:

#17 = Utf8               com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass
複製代碼

常量池主要存放字面量與符號引用。

字面量包括:

  • 文本字符串
  • 聲明爲final的常量值等

符號引用包括:

  • 類與接口的全限定名
  • 字段的名稱與描述符
  • 方法的名稱與描述符

常量池裏的每一個常量都用一個表來表示,表的結構以下所示:

cp_info {
    //表明常量類型
    u1 tag;
    //表明存儲的常量,不一樣的常量類型有不一樣的結構
    u1 info[];
}
複製代碼

目標一共有十四中常量類型,以下所示:

注:下表字段分別爲 類型、標誌(tag)、描述

  • CONSTANT_Utf8_info 1 UTF8編碼的Unicode字符串
  • CONSTANT_Integer_info 3 整型字面量
  • CONSTANT_Float_info 4 浮點型字面量
  • CONSTANT_Long_info 5 長整型字面量
  • CONSTANT_Double_info 6 雙精度浮點型字面量
  • CONSTANT_Class_info 7 類或接口的符號引用
  • CONSTANT_String_info 8 字符串類型字面量
  • CONSTANT_Fieldref_info 9 字段的符號引用
  • CONSTANT_Methodref_info 10 類中方法的符號引用
  • CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
  • CONSTANT_NameAndType_info 12 字段或方法的部分符號引用

1.4 訪問標誌

具體含義

訪問標誌:常量池以後就是訪問標誌,該標誌用於識別一些類或則接口層次的訪問信息。這些訪問信息包括這個Class是類仍是接口,是否認義Abstract類型等。

對應數值

常量池以後就是訪問標誌,前兩個字節表明訪問標誌。

從上面的分析中常量池最後一個常量是#14 = Utf8 java/lang/Object,因此它後面的兩個字節就表明訪問標誌,以下所示:

訪問表示值與含義以下所示:

  • ACC_PUBLIC 0x0001 是否爲public
  • ACC_FINAL 0x0010 是否爲final
  • ACC_SUPER 0x0020 JDK 1.0.2之後編譯出來的類該標誌位都爲真
  • ACC_INTERFACE 0x0200 是否爲接口
  • ACC_ABSTRACT 0x0400 是否爲抽象的(接口和抽象類)
  • ACC_SYNTHETIC 0x1000 表示這個代碼並不是由用戶產生的
  • ACC_ANNOTATION 0x2000 是否爲註解
  • ACC_ENUM 0x4000 是否爲枚舉

咱們上面寫了一個普通的Java類,ACC_PUBLIC位爲真,又因爲JDK 1.0.2之後編譯出來的類ACC_SUPER標誌位都爲真,因此最終的值爲:

0x0001 & 0x0020 = 0x0021
複製代碼

這個值就是上圖中的值。

1.5 類索引、父類索引與接口索引

具體含義

類索引(用來肯定該類的全限定名)、父類索引(用來肯定該類的父類的全限定名)是一個u2類型的數據(單個類、單繼承),接口索引是一個u2類型的集合(多接口實現,用來描述該類實現了哪些接口)

對應數值

類索引、父類索引與接口索引牢牢排列在訪問標誌以後。

類索引爲0x0002,它的全限定名爲com/guoxiaoxing/android/framework/demo/native_framwork/vm/TestClass。

父類索引爲0x0003,它的全限定名爲java/lang/Object。

接口索引的第一項是一個u2類型的數據表示接口計數器,表示實現接口的個數。這裏沒有實現任何接口,因此爲0x0000。

1.6 字段表集合

具體含義

字段表用來描述接口或者類裏聲明的變量、字段。包括類級變量以及實例級變量,但不包括方法內部聲明的變量。

字段表結構以下所示:

field_info {
    u2             access_flags;//訪問標誌位,例如private、public等
    u2             name_index;//字段的簡單名稱,例如int、long等
    u2             descriptor_index;//方法的描述符,描述字段的數據類型,方法的參數列表和返回值
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
複製代碼

access_flags取值以下所示:

  • ACC_PUBLIC 0x0001 是否爲 public;
  • ACC_PRIVATE 0x0002 是否爲 private;
  • ACC_PROTECTED 0x0004 是否爲 protected;
  • ACC_STATIC 0x0008 是否爲 static;
  • ACC_FINAL 0x0010 是否爲 final;
  • ACC_VOLATILE 0x0040 是否爲 volatile;
  • ACC_TRANSIENT 0x0080 是否爲 transient;
  • ACC_SYNTHETIC 0x1000 是否爲 synthetic;
  • ACC_ENUM 0x4000 是否爲enum.

descriptor_index裏描述符的含義以下所示:

  • B byte
  • C char
  • D double
  • F float
  • I int
  • J long
  • S short
  • Z boolean
  • V void
  • L Object, 例如 Ljava/lang/Object

對應數值

  • 第一個u2類型的值爲0x0001,表明當前容器計數器field_count爲1,說明這個類只有一個字段表數據。也就是咱們上面定義的類成員變量private int m;
  • 第二個u2類型的值爲0x0002,表明access_flags,說明這個成員變量的類型爲private。
  • 第三個u2類型的值爲0x0005,表明name_index爲5。
  • 第四個u2類型的值爲0x0006,表明descriptor_index爲6。

1.7 方法表集合

方法便用來描述方法相關信息。

方法表的類型與字段表徹底相同,以下所示:

method_info {
    u2             access_flags;//訪問標誌位,例如private、public等
    u2             name_index;//方法名
    u2             descriptor_index;//方法的描述符,描述字段的數據類型,方法的參數列表和返回值
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
複製代碼

對應的值

  • 第一個u2類型的值爲0x0002,表明當前類有兩個方法,即爲構造函數和咱們上面寫的inc()方法。
  • 第二個u2類型的值爲0x0001,表明access_flags,即方法的訪問類型爲public。
  • 第三個u2類型的值爲0x0007,表明name_index,即爲。
  • 第四個u2類型的值爲0x0008,表明descriptor_index,即爲()V。
  • 第五個u2類型的值爲0x0001,表明attributes_count,表示該方法的屬性集合有一項屬性。
  • 第六個u2類型的值爲0x0009,表明屬性名稱,對應常量"code",表明此屬性是方法的字節碼描述。

後續還有屬性表集合等相關信息,這裏就再也不贅述,更多內容請參見Java虛擬機規範(Java SE 7).pdf

經過上面的描述,咱們理解了Class存儲格式的細節,那麼這些是如何被加載到虛擬機中去的呢,加載到虛擬機以後又會發生什麼變化呢?🤔

咱們接着來看。

二 類的加載流程

什麼是類的加載?🤔

類的加載就是虛擬機經過一個類的全限定名來獲取描述此類的二進制字節流。

類加載的流程圖以下所示:

加載

  1. 經過一個類的全限定名來獲取此類的二進制流。
  2. 將這個字節流所表明的靜態存儲結構轉換爲方法去的運行時數據結構。
  3. 在內存中生成一個代碼這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。

事實上,從哪裏將一個類加載成二進制流是有很開發的,具體說來:

  • 從zip包中讀取,這就發展成了咱們常見的JAR、AAR依賴。
  • 運行時動態生成,這是咱們常見的動態代理技術,在java.reflect.Proxy中就是用ProxyGenerateProxyClass來爲特定接口生成代理類的二進制流。

驗證

驗證主要是驗證加載進來的字節碼二進制流是否符合虛擬機規範。

  1. 文件格式驗證:驗證字節碼流是否符合Class文件格式的規範,而且可以被當前版本的虛擬機處理。
  2. 元數據驗證:對字節碼描述的語義進行分析,以保證其描述的信息符合Java語言規範的要求。
  3. 字節碼驗證:對字節碼的數據流和控制流進行分析,肯定程序語義是合法的,符合邏輯的。
  4. 符號引用驗證:這個階段在解析階段中完成,虛擬機將符號引用轉換爲直接引用。

準備

準備階段正式爲類變量分爲內存並設置變量的初始值,所使用的內存在方法去裏被分配,這些變量指的是被static修飾的變量,而不包括實例的變量,實例的變量會伴隨着對象的實例化一塊兒在Java堆 中分配。

解析

解析階段將符號引用轉換爲直接引用,符號引用咱們前面已經說過,它以CONSTANT_class_info等符號來描述引用的目標,而直接引用指的是這些符號引用加載到虛擬機中之後 的內存地址。

這裏的解析主要是針對咱們上面提到的字段表、方法表、屬性表裏面的信息,具體說來,包括如下類型:

  • 接口
  • 字段
  • 類方法
  • 接口方法
  • 方法類型
  • 方法句柄
  • 調用點限定符

初始化

初始化階段開始執行類構造器()方法,該方法是由全部類變量的賦值動做和static語句塊合併產生的

關於類構造器()方法,它和實例構造器()是不一樣的,關於這個方法咱們須要注意如下幾點:

  • 類構造器()方法與實例構造器()方法不一樣,不須要顯式的調用父類的構造器,虛擬機會保證父類構造器先執行。
  • 類構造器()方法對於類或者接口不是必須的,若是一個類既沒有賦值操做,也沒有靜態語句塊,則不會生成該方法。
  • 接口能夠有變量初始化的賦值操做,所以接口也能夠生成clinit>()方法、
  • 虛擬機會保證一個類的()方法在多線程環境下可以被正確的加鎖和同步。若是多個線程同時去初始化一個類,那麼只會有一個線程執行該類的clinit>()方法 ,其餘線程會被阻塞。

講完了類的加載流程,咱們接着來看看類加載器。

三 類加載器

3.1 Java虛擬機類加載機制

類的加載就是虛擬機經過一個類的全限定名來獲取描述此類的二進制字節流,而完成這個加載動做的就是類加載器。

類和類加載器息息相關,斷定兩個類是否相等,只有在這兩個類被同一個類加載器加載的狀況下才有意義,不然即使是兩個類來自同一個Class文件,被不一樣類加載器加載,它們也是不相等的。

注:這裏的相等性保函Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果以及Instance關鍵字對對象所屬關係的斷定結果等。

類加載器能夠分爲三類:

  • 啓動類加載器(Bootstrap ClassLoader):負責加載<JAVA_HOME>\lib目錄下或者被-Xbootclasspath參數所指定的路徑的,而且是被虛擬機所識別的庫到內存中。
  • 擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\lib\ext目錄下或者被java.ext.dirs系統變量所指定的路徑的全部類庫到內存中。
  • 應用類加載器(Application ClassLoader):負責加載用戶類路徑上的指定類庫,若是應用程序中沒有實現本身的類加載器,通常就是這個類加載器去加載應用程序中的類庫。

這麼多類加載器,那麼當類在加載的時候會使用哪一個加載器呢?🤔

這個時候就要提到類加載器的雙親委派模型,流程圖以下所示:

雙親委派模型的整個工做流程很是的簡單,以下所示:

若是一個類加載器收到了加載類的請求,它不會本身當即去加載類,它會先去請求父類加載器,每一個層次的類加載器都是如此。層層傳遞,直到傳遞到最高層的類加載器,只有當 父類加載器反饋本身沒法加載這個類,纔會有當前子類加載器去加載該類。

關於雙親委派機制,在ClassLoader源碼裏也能夠看出,以下所示:

public abstract class ClassLoader {
    
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            //首先,檢查該類是否已經被加載
            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) {
                    //若是父類加載器沒有加載到該類,則本身去執行加載
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }
}
複製代碼

爲何要這麼作呢?🤔

這是爲了要讓越基礎的類由越高層的類加載器加載,例如Object類,不管哪一個類加載器去嘗試加載這個類,最終都會傳遞給最高層的類加載器去加載,前面咱們也說過,類的相等性是由 類與其類加載器共同斷定的,這樣Object類不管在何種類加載器環境下都是同一個類。

相反若是沒有雙親委派模型,那麼每一個類加載器都會去加載Object,那麼系統中就會出現多個不一樣的Object類了,如此一來系統的最基礎的行爲也就沒法保證了。

理解了JVM上的類加載機制,咱們再來看看Android虛擬機上上是如何加載類的。

3.2 Android虛擬機類加載機制

Java虛擬機加載的是class文件,而Android虛擬機加載的是dex文件(多個class文件合併而成),因此二者既有類似的地方,也有所不一樣。

Android類加載器類圖以下所示:

能夠看到Android類加載器的基類是BaseDexClassLoader,它有派生出兩個子類加載器:

  • PathClassLoader: 主要用於系統和app的類加載器,其中optimizedDirectory爲null, 採用默認目錄/data/dalvik-cache/
  • DexClassLoader: 能夠從包含classes.dex的jar或者apk中,加載類的類加載器, 可用於執行動態加載, 但必須是app私有可寫目錄來緩存odex文件. 可以加載系統沒有安裝的apk或者jar文件, 所以不少插件化方案都是採用DexClassLoader;

除了這兩個子類覺得,還有兩個類:

  • DexPathList:就跟它的名字那樣,該類主要用來查找Dex、SO庫的路徑,並這些路徑總體呈一個數組。
  • DexFile:用來描述Dex文件,Dex的加載以及Class額查找都是由該類調用它的native方法完成的。

咱們先來看看基類BaseDexClassLoader的構造方法

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
複製代碼

BaseDexClassLoader構造方法的四個參數的含義以下:

  • dexPath:指的是在Androdi包含類和資源的jar/apk類型的文件集合,指的是包含dex文件。多個文件用「:」分隔開,用代碼就是File.pathSeparator。
  • optimizedDirectory:指的是odex優化文件存放的路徑,能夠爲null,那麼就採用默認的系統路徑。
  • libraryPath:指的是native庫文件存放目錄,也是以「:」分隔。
  • parent:parent類加載器

DexClassLoader與PathClassLoader都繼承於BaseDexClassLoader,這兩個類只是提供了本身的構造函數,沒有額外的實現,咱們對比下它們的構造函數的區別。

PathClassLoader

public class PathClassLoader extends BaseDexClassLoader {
    
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
複製代碼

DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {
    
   public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}
複製代碼

能夠發現這兩個類的構造函數最大的差異就是DexClassLoader提供了optimizedDirectory,而PathClassLoader則沒有,optimizedDirectory正是用來存放odex文件 的地方,之後能夠利用DexClassLoader實現動態加載。

上面咱們也說過,Dex的加載以及Class額查找都是由DexFile調用它的native方法完成的,咱們來看看它的實現。

咱們來看看Dex文件加載、類的查找加載的序列圖,以下所示:

從上圖Dex加載的流程能夠看出,optimizedDirectory決定了調用哪個DexFile的構造函數。

若是optimizedDirectory爲空,這個時候實際上是PathClassLoader,則調用:

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
        throws IOException {
    this(file.getPath(), loader, elements);
}
複製代碼

若是optimizedDirectory不爲空,這個時候實際上是DexClassLoader,則調用:

private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    if (outputName != null) {
        try {
            String parent = new File(outputName).getParent();
            if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                throw new IllegalArgumentException("Optimized data directory " + parent
                        + " is not owned by the current user. Shared storage cannot protect"
                        + " your application from code injection attacks.");
            }
        } catch (ErrnoException ignored) {
            // assume we'll fail with a more contextual error later
        }
    }

    mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
    mFileName = sourceName;
    //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
複製代碼

因此你能夠看到DexClassLoader在加載Dex文件的時候比PathClassLoader多了一個openDexFile()方法,該方法調用的是native方法openDexFileNative()方法。

👉 dalvik_system_DexFile.cpp

這個方法並非真的打開Dex文件,而是將Dex文件以一種mmap的方式映射到虛擬機進程的地址空間中去,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關係。實現這樣的映射關係後,虛擬機 進程就能夠採用指針的方式讀寫操做這一段內存,而系統會自動回寫髒頁面到對應的文件磁盤上,即完成了對文件的操做而沒必要再調用read,write等系統調用函數。

關於mmap,它是一種頗有用的文件讀寫方式,限於篇幅這裏再也不展開,更多關於mmap的內容能夠參見文章:http://www.cnblogs.com/huxiao-tee/p/4660352.html

到這裏,Android虛擬機的類加載機制就講的差很少了,咱們再來總結一下。

Android虛擬機有兩個類加載器DexClassLoader與PathClassLoader,它們都繼承於BaseDexClassLoader,它們內部都維護了一個DexPathList的對象,DexPathList主要用來存放指明包含dex文件、native庫和優化odex目錄。 Dex文件採用DexFile這個類來描述,Dex的加載以及類的查找都是經過DexFile調用它的native方法來完成的。

相關文章
相關標籤/搜索