Android執行時ART載入類和方法的過程分析

發表於2015-07-16

在前面Android執行時ART載入OAT檔案的過程分析這篇文章的最後,我們簡單總結了ART執行時查詢類方法的本地機器指令的過程,如圖1所示:

圖1 ART執行時查詢類方法的本地機器指令的過程

       為了方便描述,我們將DEX檔案中描述的類和方法稱為DEX類(Dex Class)和DEX方法(Dex Method),而將在OAT檔案中描述的類和方法稱為OAT類(Oat Class)和OAT方法(Oat Method)。接下來我們還會看到,ART執行時在內部又會使用另外兩個不同的術語來描述類和方法,其中將類描述為Class,而將類方法描述為ArtMethod。

在圖1中,為了找到一個類方法的本地機器指令,我們需要執行以下的操作:

1. 在DEX檔案中找到目標DEX類的編號,並且以這個編號為索引,在OAT檔案中找到對應的OAT類。

2. 在DEX檔案中找到目標DEX方法的編號,並且以這個編號為索引,在上一步找到的OAT類中找到對應的OAT方法。

3. 使用上一步找到的OAT方法的成員變數begin_和code_offset_,計算出該方法對應的本地機器指令。

通過前面Android執行時ART簡要介紹和學習計劃一文的學習,我們可以知道,ART執行時的入口是com.android.internal.os.ZygoteInit類的靜態成員函式main,如下所示:

這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。

在AndroidRuntime類的成員函式start中,首先是通過呼叫函式startVm建立了一個Java虛擬機器mJavaVM及其JNI介面env。這個Java虛擬機器實際上就是ART執行時。在接下來的描述中,我們將不區分ART虛擬機器和ART執行時,並且認為它們表達的是同一個概念。獲得了ART虛擬機器的JNI介面之後,就可以通過它提供的函式FindClass和GetStaticMethodID來載入com.android.internal.os.ZygoteInit類及其靜態成員函式main。於是,最後就可以再通過JNI介面提供的函式CallStaticVoidMethod來呼叫com.android.internal.os.ZygoteInit類的靜態成員函式main,以及進行到ART虛擬機器裡面去執行。

接下來,我們就通過分析JNI介面FindClass和GetStaticMethodID的實現,以便理解ART執行時是如何查詢到指定的類和方法的。在接下來的一篇文章中,我們再分析ART執行時是如何通過JNI介面CallStaticVoidMethod來執行指定類方法的本地機器指令的。

在分析JNI介面FindClass和GetStaticMethodID的實現之前,我們先要講清楚JNI介面是如何建立的。從前面Android執行時ART載入OAT檔案的過程分析一文可以知道,與ART虛擬機器主執行緒關聯的JNI介面是在函式JNI_CreateJavaVM中建立的,如下所示:

這個函式定義在檔案art/runtime/jni_internal.cc中。

呼叫Thread類的靜態成員函式Current獲得的是用來描述當前執行緒(即ART虛擬機器的主執行緒)的一個Thread物件,再通過呼叫這個Thread物件的成員函式GetJniEnv就獲得一個JNI介面,並且儲存在輸出引數p_env中。

Thread類的成員函式GetJniEnv的實現如下所示:

這個函式定義在檔案art/runtime/thread.h中。

Thread類的成員函式GetJniEnv返回的是成員變數jni_env_指向的一個JNIEnvExt物件。

JNIEnvExt類是從JNIEnv類繼承下來的,如下所示:

這個類定義在檔案art/runtime/jni_internal.h。

JNIEnv類定義了JNI介面,如下所示:

這個類定義在檔案libnativehelper/include/nativehelper/jni.h中。

在JNIEnv類中,最重要的就是成員變數functions了,它指向的是一個型別為JNINativeInterface的JNI函式表。所有的JNI介面呼叫都是通過這個JNI函式表來實現的。例如,用來獲得版本號的JNI介面GetVersion就是通過呼叫JNI函式表中的GetVersion函式來實現的。

那麼,上述的JNI函式表是如何建立的呢?通過JNIEnvExt類的建構函式可以知道答案,如下所示:

這個函式定義在檔案art/runtime/jni_internal.cc中。

JNIEnvExt類的建構函式將父類JNIEnv的成員變數functions初始化為全域性變數gJniNativeInterface。也就是說,JNI函式表實際是由全域性變數gJniNativeInterface來描述的。

全域性變數gJniNativeInterface的定義如下所示:

這個全域性變數定義在檔案art/runtime/jni_internal.cc中。

從這裡可以看出,JNI函式表實際上是由JNI類的靜態成員函式組成的。例如,JNI函式GetVersion是由JNI類的靜態成員函式GetVersion來實現的。理解了這一點之後,我們就輕鬆地知道同接下來我們要分析的JNI介面FindClass和GetStaticMethodID分別是由JNI類的靜態成員函式FindClass和GetStaticMethodID來實現的。事實上,如果讀者看過Dalvik虛擬機器的啟動過程分析這篇文章,那麼對上述的JNI介面定義是一目瞭然的。

JNI類的靜態成員函式FindClass的實現如下所示:

這個函式定義在檔案art/runtime/jni_internal.cc中。

在ART虛擬機器程式中,存在著一個Runtime單例,用來描述ART執行時。通過呼叫Runtime類的靜態成員函式Current可以獲得上述Runtime單例。獲得了這個單例之後,就可以呼叫它的成員函式GetClassLinker來獲得一個ClassLinker物件。從前面Android執行時ART載入OAT檔案的過程分析一文可以知道。上述ClassLinker物件是在建立ART虛擬機器的過程中建立的,用來載入類以及連結類方法。

JNI類的靜態成員函式FindClass首先是判斷ART執行時是否已經啟動起來。如果已經啟動,那麼就通過呼叫函式GetClassLoader來獲得當前執行緒所關聯的ClassLoader,並且以此為引數,呼叫前面獲得的ClassLinker物件的成員函式FindClass來載入由引數name指定的類。一般來說,當前執行緒所關聯的ClassLoader就是當前正在執行的類方法所關聯的ClassLoader,即用來載入當前正在執行的類的ClassLoader。如果ART虛擬機器還沒有開始執行類方法,就像我們現在這個場景,那麼當前執行緒所關聯的ClassLoader實際上就係統類載入器,即SystemClassLoader。

如果ART執行時還沒有啟動,那麼這時候只可以載入系統類。這個通過前面獲得的ClassLinker物件的成員函式FindSystemClass來實現的。在我們這個場景中,ART執行時已經啟動,因此,接下來我們就繼續分析ClassLinker類的成員函式FindClass的實現。

ClassLinker類的成員函式FindClass的實現如下所示:

這個函式定義在檔案art/runtime/class_linker.cc中。

引數descriptor指向的是要載入的類的簽名,而引數class_loader指向的是一個類載入器,我們假設它的值不為空,並且指向系統類載入器。

ClassLinker類的成員函式FindClass首先是呼叫另外一個成員函式LookupClass來檢查引數descriptor指定的類是否已經被載入過。如果是的話,那麼ClassLinker類的成員函式LookupClass就會返回一個對應的Class物件,這個Class物件接著就會返回給呼叫者,表示載入已經完成。

如果引數descriptor指定的類還沒有被載入過,這時候主要就是要看引數class_loader的值了。如果引數class_loader的值等於NULL,那麼就需要呼叫DexFile類的靜態FindInClassPath來在系統啟動類路徑尋找對應的類。一旦尋找到,那麼就會獲得包含目標類的DEX檔案,因此下來就呼叫ClassLinker類的另外一個成員函式DefineClass從獲得的DEX檔案中載入引數descriptor指定的類了。

如果引數class_loader的值不等於NULL,也就是說ClassLinker類的成員函式FindClass的呼叫者指定了類載入器,那麼就通過該類載入器來載入引數descriptor指定的類。每一個類載入器在Java層都對應有一個java.lang.ClassLoader物件。通過呼叫這個java.lang.ClassLoader類的成員函式loadClass即可載入指定的類。在我們這個場景中,上述的java.lang.ClassLoader類是一個系統類載入器,它負責載入系統類。而我們當前要載入的類為com.android.internal.os.ZygoteInit,它屬於一個系統類。

系統類載入器在載入系統類實際上也是通過JNI方法呼叫ClassLinker類的成員函式FindClass來實現的。只不過這時候傳進來的引數class_loader是一個NULL值。這樣,ClassLinker類的成員函式FindClass就會在系統啟動類路徑中尋找引數descriptor指定的類可以在哪一個DEX檔案載入,這是通過呼叫DexFile類的靜態成員函式FindInClassPath來實現的。

所謂的系統啟動類路徑,其實就是一系列指定的由系統提供的DEX檔案,這些DEX檔案儲存在ClassLinker類的成員變數boot_class_path_描述的一個向量中。那麼問題就來了,這些DEX檔案是怎麼來的呢?我們知道,在ART執行時中,我們使用的是OAT檔案。如果看過前面Android執行時ART載入OAT檔案的過程分析這篇文章,就會很容易知道,OAT檔案裡面包含有DEX檔案。而且ART執行時在啟動的時候,會載入一個名稱為system@framework@boot.art@classes.oat的OAT檔案。這個OAT檔案包含有多個DEX檔案,每一個DEX檔案都是一個系統啟動類路徑,它們會被新增到ClassLinker類的成員變數boot_class_path_描述的向量中去。

這裡呼叫DexFile類的靜態成員函式FindInClassPath,實際要完成的工作就是從ClassLinker類的成員變數boot_class_path_描述的一系列的DEX檔案中檢查哪一個DEX檔案包含有引數descriptor指定的類。這可以通過解析DEX檔案來實現,關於DEX檔案的格式,可以參考官方文件:http://source.android.com/tech/dalvik/index.html

知道了引數descriptor指定的類定義在哪一個DEX檔案之後,就可以通過ClassLinker類的另外一個成員函式DefineClass來從中載入它了。接下來,我們就繼續分析ClassLinker類的成員函式DefineClass的實現,如下所示:

這個函式定義在檔案art/runtime/class_linker.cc中。

ClassLinker類有一個型別為bool的成員變數init_done_,用來表示ClassLinker是否已經初始化完成。ClassLinker在建立的時候,有一個初始化過程,用來建立一些內部類。這些內部類要麼是手動建立的,要麼是從Image空間獲得的。關於ART虛擬機器的Image空間,我們在後面分析ART垃圾收集機制的文章中再詳細分析。

呼叫ClassLinker類的成員函式DefineClass的時候,如果ClassLinker正處於初始化過程,即其成員變數init_done_的值等於false,並且引數descriptor描述的是特定的內部類,那麼就將本地變數klass指向它們,其餘情況則會通過成員函式AllocClass為其分配儲存空間,以便後面通過成員函式LoadClass進行初始化。

ClassLinker類的成員函式LoadClass用來從指定的DEX檔案中載入指定的類。指定的類從DEX檔案中載入完成後,需要通過另外一個成員函式InsertClass新增到ClassLinker的已載入類列表中去。如果指定的類之前已經載入過,即呼叫成員函式InsertClass得到的返回值不等於空,那麼就說明有另外的一個執行緒也正在載入指定的類。這時候就需要呼叫成員函式EnsureResolved來保證(等待)該類已經載入並且解析完成。另一方面,如果沒有其它執行緒載入指定的類,那麼當前執行緒從指定的DEX檔案載入完成指定的類後,還需要呼叫成員函式LinkClass來對載入後的類進行解析。最後,一個型別為Class的物件就可以返回給呼叫者了,用來表示一個已經載入和解析完成的類。

接下來,我們主要分析ClassLinker類的成員函式LoadClass的實現,以便可以瞭解類的載入過程。

ClassLinker類的成員函式LoadClass的實現如下所示:

這個函式定義在檔案art/runtime/class_linker.cc中。

我們首先要明確一下各個引數的含義:

dex_file: 型別為DexFile,描述要載入的類所在的DEX檔案。

dex_class_def: 型別為ClassDef,描述要載入的類在DEX檔案裡面的資訊。

klass: 型別為Class,描述載入完成的類。

class_loader:  型別為ClassLoader,描述所使用的類載入器。

總的來說,ClassLinker類的成員函式LoadClass的任務就是要用dex_file、dex_class_def、class_loader三個引數包含的相關資訊設定到引數klass描述的Class物件去,以便可以得到一個完整的已載入類資訊。

ClassLinker類的成員函式LoadClass主要完成的工作如下所示:

1. 將引數class_loader描述的ClassLoader設定到klass描述的Class物件中去,即給每一個已載入類關聯一個類載入器。

2. 通過DexFile類的成員函式GetIndexForClassDef獲得正在載入的類在DEX檔案中的類索引號,並且設定到klass描述的Class物件中去。這個類索引號是一個很重要的資訊,因為我們需要通過類索引號在相應的OAT檔案找到一個OatClass結構體。有了這個OatClass結構體之後,我們才可以找到類方法對應的本地機器指令。具體可以參考前面圖1和Android執行時ART載入OAT檔案的過程分析一文。

3. 從引數dex_file描述的DEX檔案中獲得正在載入的類的靜態成員變數和例項成員變數個數,並且為每一個靜態成員變數和例項成員變數都分配一個ArtField物件,接著通過ClassLinker類的成員函式LoadField對這些ArtField物件進行初始化。初始好得到的ArtField物件全部儲存在klass描述的Class物件中。

4. 呼叫ClassLinker類的成員函式GetOatClass,從相應的OAT檔案中找到與正在載入的類對應的一個OatClass結構體oat_class。這需要利用到上面提到的DEX類索引號,這是因為DEX類和OAT類根據索引號存在一一對應關係。這一點可以參考圖1和Android執行時ART載入OAT檔案的過程分析一文。

5.  從引數dex_file描述的DEX檔案中獲得正在載入的類的直接成員函式和虛擬成員函式個數,並且為每一個直接成員函式和虛擬成員函式都分配一個ArtMethod物件,接著通過ClassLinker類的成員函式LoadMethod對這些ArtMethod物件進行初始化。初始好得到的ArtMethod物件全部儲存在klass描述的Class物件中。

6.  每一個直接成員函式和虛擬成員函式都對應有一個函式索引號。根據這個函式索引號可以在第4步得到的OatClass結構體中找到對應的本地機器指令,具體可以參考前面圖1和Android執行時ART載入OAT檔案的過程分析一文。所有與這些成員函式關聯的本地機器指令資訊通過全域性函式LoadCode設定到klass描述的Class物件中。

總結來說,引數klass描述的Class物件包含了一系列的ArtField物件和ArtMethod物件,其中,ArtField物件用來描述成員變數資訊,而ArtMethod用來描述成員函式資訊。

接下來,我們繼續分析全域性函式LinkCode的實現,以便可以瞭解如何在一個OAT檔案中找到一個DEX類方法的本地機器指令。

函式LinkCode的實現如下所示:

這個函式定義在檔案art/runtime/class_linker.cc中。

引數method表示要設定本地機器指令的類方法,引數oat_class表示類方法method在OAT檔案中對應的OatClass結構體,引數method_index表示類方法method的索引號。

通過引數method_index描述的索引號可以在oat_class表示的OatClass結構體中找到一個OatMethod結構體oat_method。這個OatMethod結構描述了類方法method的本地機器指令相關資訊,通過呼叫它的成員函式LinkMethod可以將這些資訊設定到引數method描述的ArtMethod物件中去。如下所示:

這個函式定義在檔案art/runtime/oat_file.cc中。

其中,最重要的就是通過OatMethod類的成員函式GetCode獲得OatMethod結構體中的code_offset_欄位,並且通過呼叫ArtMethod類的成員函式SetEntryPointFromCompiledCode設定到引數method描述的ArtMethod物件中去。OatMethod結構體中的code_offset_欄位指向的是一個本地機器指令函式,這個本地機器指令函式正是通過翻譯引數method描述的類方法的DEX位元組碼得到的。

回到函式LinkCode中,它接著呼叫另外一個全域性函式NeedsInterpreter檢查引數method描述的類方法是否需要通過直譯器執行,它的實現如下所示:

這個函式定義在檔案art/runtime/class_linker.cc中。

在以下兩種情況下,一個類方法需要通過直譯器來執行:

1. 沒有對應的本地機器指令,即引數code的值等於NULL。

2. ART虛擬機器執行在解釋模式中,並且類方法不是JNI方法,並且也不是代理方法。

呼叫Runtime類的靜態成員函式Current獲得的是描述ART執行時的一個Runtime物件。呼叫這個Runtime物件的成員函式GetInstrumentation獲得的是一個Instrumentation物件。這個Instrumentation物件是用來除錯ART執行時的,通過呼叫它的成員函式InterpretOnly可以知道ART虛擬機器是否執行在解釋模式中。

因為JNI方法是沒有對應的DEX位元組碼的,因此即使ART虛擬機器執行在解釋模式中,JNI方法也不能通過直譯器來執行。至於代理方法,由於是動態生成的(沒有對應的DEX位元組碼),因此即使ART虛擬機器執行在解釋模式中,它們也不通過直譯器來執行(這一點猜測的,還沒有確認)。

回到函式LinkCode中,如果呼叫函式NeedsInterpreter得到的返回值enter_interpreter等於true,那麼就意味著引數method描述的類方法需要通過直譯器來執行,這時候就將函式artInterpreterToInterpreterBridge設定為直譯器執行該類方法的入口點。否則的話,就將另外一個函式artInterpreterToCompiledCodeBridge設定為直譯器執行該類方法的入口點。

為什麼我們需要為類方法設定直譯器入口點呢?根據前面的分析可以知道,在ART虛擬機器中,並不是所有的類方法都是有對應的本地機器指令的,並且即使一個類方法有對應的本地機器指令,當ART虛擬機器以解釋模式執行時,它也需要通過直譯器來執行。當以直譯器執行的類方法在執行的過程中呼叫了其它的類方法時,直譯器就需要進一步知道被呼叫的類方法是應用以解釋方式執行,還是本地機器指令方法執行。為了能夠進行統一處理,就給每一個類方法都設定一個直譯器入口點。需要通過解釋執行的類方法的直譯器入口點函式是artInterpreterToInterpreterBridge,它會繼續通過直譯器來執行該類方法。需要通過本地機器指令執行的類方法的直譯器入口點函式是artInterpreterToCompiledCodeBridge,它會間接地呼叫該類方法的本地機器指令。

函式LinkCode繼續往下執行,判斷引數method描述的類方法是否是一個抽象方法。抽象方法宣告類中是沒有實現的,必須要由子類實現。因此抽象方法在宣告類中是沒有對應的本地機器指令的,它們必須要通過直譯器來執行。不過,為了能夠進行統一處理,我們仍然假裝抽象方法有對應的本地機器指令函式,只不過這個本地機器指令函式被設定為GetCompiledCodeToInterpreterBridge。當函式GetCompiledCodeToInterpreterBridge被呼叫時,就會自動進入到直譯器中去。

對於非抽象方法,函式LinkCode還要繼續往下處理。到這裡有一點是需要注意的,前面通過呼叫OatMethod類的成員函式LinkMethod,我們已經設定好引數method描述的類方法的本地機器指令了。但是,在以下兩種情況下,我們需要進行調整:

1. 當引數method描述的類方法是一個非類靜態初始化函式(class initializer)的靜態方法時,我們不能直接執行翻譯其DEX位元組碼得到的本地機器指令。這是因為類靜態方法可以在不建立類物件的前提下執行。這意味著一個類靜態方法在執行的時候,對應的類可能還沒有初始化好。這時候我們就需要先將對應的類初始化好,再執行相應的靜態方法。為了能夠做到這一點。我們就先呼叫GetResolutionTrampoline函式得到一個Tampoline函式,接著將這個Trampoline函式作為靜態方法的本地機器指令。這樣如果類靜態方法在對應的類初始化前被呼叫,就會觸發上述的Trampoline函式被執行。而當上述Trampoline函式執行時,它們先初始化好對應的類,再呼叫原來的類靜態方法對應的本地機器指令。按照程式碼中的註釋,當一個類初始化完成之後,就可以呼叫函式ClassLinker::FixupStaticTrampolines來修復該類的靜態成員函式的本地機器指令,也是通過翻譯DEX位元組碼得到的本地機器指令。這裡需要注意的是,為什麼類靜態初始化函式不需要按照按照其它的類靜態方法一樣設定Tampoline函式呢?這是因為類靜態初始化函式是一定保證是在類初始化過程中執行的。

2.  當引數method描述的類方法需要通過直譯器執行時,那麼當該類方法執行時,就不能執行它的本地機器指令,因此我們就先呼叫GetCompiledCodeToInterpreterBridge函式獲得一個橋接函式,並且將這個橋接函式假裝為類方法的本地機器指令。一旦該橋接函式被執行,它就會入到直譯器去執行類方法。通過這種方式,我們就可以以統一的方法來呼叫解釋執行和本地機器指令執行的類方法。

函式LinkCode接下來繼續判斷引數method描述的類方法是否是一個JNI方法。如果是的話,那麼就呼叫ArtMethod類的成員函式UnregisterNative來初始化它的JNI方法呼叫介面。ArtMethod類的成員函式UnregisterNative的實現如下所示:

這個函式定義在檔案runtime/mirror/art_method.cc中。

ArtMethod類的成員函式UnregisterNative實際上就是將一個JNI方法的初始化入口設定為通過呼叫函式GetJniDlsymLookupStub獲得的一個Stub。這個Stub的作用是,當一個JNI方法被呼叫時,如果還沒有顯示地註冊有Native函式,那麼它就會自動從已載入的SO檔案查詢是否存在一個對應的Native函式。如果存在的話,就將它註冊為JNI方法的Native函式,並且執行它。這就是隱式的JNI方法註冊。

回到函式LinkCode,它最後呼叫Instrumentation類的成員函式UpdateMethodsCode檢查是否要進一步修改引數method描述的類方法的本地機器指令入口,它的實現如下所示:

這個函式定義在檔案art/runtime/instrumentation.cc中。

Instrumentation類是用來呼叫ART執行時的。例如,當我們需要監控類方法的呼叫時,就可以往Instrumentation註冊一些Listener。這樣當類方法呼叫時,這些註冊的Listener就會得到回撥。當Instrumentation註冊有相應的Listener時,它的成員變數instrumentation_stubs_installed_的值就會等於true。

因此,當Instrumentation類的成員變數instrumentation_stubs_installed_的值等於true時,我們需要使用一個監控函式來替換掉類方法原來的本地機器指令。這樣當類方法被呼叫時,監控函式就獲得控制權,它可以在呼叫原來的本地機器指令前後,向註冊的Listener發出通知。

對於JNI方法,我們通過呼叫函式GetQuickInstrumentationEntryPoint獲得的函式作為其監控函式;而對其它的類方法,我們通過呼叫函式GetCompiledCodeToInterpreterBridge獲得的函式作為其監控函式。

另一方面,如果沒有Listener註冊到Instrumentation中,即它的成員變數instrumentation_stubs_installed_的值等於false,那麼Instrumentation類的成員函UpdateMethodsCode就會使用引數code描述的本地機器指令作為引數method描述的類方法的本地機器指令入口。引數code描述的本地機器指一般就是翻譯類方法的DEX位元組碼得到的本地機器指令了。實際上是相當於沒有修改類方法的本地機器指令入口。

這樣,一個類的載入過程就完成了。載入完成後,得到的是一個Class物件。這個Class物件關聯有一系列的ArtField物件和ArtMethod物件。其中,ArtField物件描述的是成員變數,而ArtMethod物件描述的是成員函式。對於每一個ArtMethod物件,它都有一個直譯器入口點和一個本地機器指令入口點。這樣,無論一個類方法是通過直譯器執行,還是直接以本地機器指令執行,我們都可以以統一的方式來進行呼叫。同時,理解了上述的類載入過程後,我們就可以知道,我們在Native層通過JNI介面FindClass查詢或者載入類時,得到的一個不透明的jclass值,實際上指向的是一個Class物件。

有了類載入過程的知識後,接下來我們再繼續分析類方法的查詢過程,也就是分析JNI介面GetStaticMethodID的實現。按照前面的分析,JNI介面GetStaticMethodID是由JNI類的靜態成員函式GetStaticMethodID實現的。因此,接下來我們就開始分析JNI類的靜態成員函式GetStaticMethodID的實現。

JNI類的靜態成員函式GetStaticMethodID的實現如下所示:

這個函式定義在檔案art/runtime/jni_internal.cc中。

引數name和sig描述的分別是要查詢的類方法的名稱和簽名,而引數java_class的是對應的類。引數java_class的型別是jclass,從前面類載入過程的分析可以知道,它實際上指向的是一個Class物件。

JNI類的靜態成員函式GetStaticMethodID通過呼叫一個全域性函式FindMethodID來查詢指定的類,後者的實現如下所示:

這個函式定義在檔案art/runtime/jni_internal.cc。

函式FindMethodID的執行過程如下所示:

1. 將引數jni_class的值轉換為一個Class指標c,因此就可以得到一個Class物件,並且通過ClassLinker類的成員函式EnsureInitialized確保該Class物件描述的類已經初始化。

2. Class物件c描述的類在載入的過程中,經過解析已經關聯上一系列的成員函式。這些成員函式可以分為兩類:Direct和Virtual。Direct類的成員函式包括所有的靜態成員函式、私有成員函式和建構函式,而Virtual則包括所有的虛成員函式。因此:

2.1. 當引數is_static的值等於true時,那麼就表示要查詢的是靜態成員函式,這時候就在Class物件c描述的類的關聯的Direct成員函式列表中查詢引數name和sig對應的成員函式。這是通過呼叫Class類的成員函式FindDirectMethod來實現的。

2.2. 當引數is_static的值不等於true時,那麼就表示要查詢的是虛擬成員函式或者非靜態的Direct成員函式,這時候先在Class物件c描述的類的關聯的Virtual成員函式列表中查詢引數name和sig對應的成員函式。這是通過呼叫Class類的成員函式FindVirtualMethod來實現的。如果找不到對應的虛擬成員函式,那麼再在Class物件c描述的類的關聯的Direct成員函式列表中查詢引數name和sig對應的成員函式。

3. 經過前面的查詢過程,如果都不能在Class物件c描述的類中找到與引數name和sig對應的成員函式,那麼就丟擲一個NoSuchMethodError異常。否則的話,就將查詢得到的ArtMethod物件封裝成一個jmethodID值返回給呼叫者。

也就是說,我們通過呼叫JNI介面GetStaticMethodID獲得的不透明jmethodID值指向的實際上是一個ArtMethod物件。得益於前面的類載入過程,當我們獲得了一個ArtMethod物件之後,就可以輕鬆地得到它的本地機器指令入口,進而對它進行執行。

這樣,我們就分析完成類方法的查詢過程了。在接下來的一篇文章中,我們將繼續分析類方法的本地機器指令的呼叫過程。通過對類方法的本地機器指令的呼叫過程的理解,可以進一步理解ART虛擬機器的執行原理。敬請關注!更多資訊也可以關注老羅的新浪微博:http://weibo.com/shengyangluo

相關文章