Dalvik虛擬機器的啟動過程分析
在Android系統中,應用程式程序都是由Zygote程序孵化出來的,而Zygote程序是由Init程序啟動的。Zygote程序在啟動時會建立一個Dalvik虛擬機器例項,每當它孵化一個新的應用程式程序時,都會將這個Dalvik虛擬機器例項複製到新的應用程式程序裡面去,從而使得每一個應用程式程序都有一個獨立的Dalvik虛擬機器例項。在本文中,我們就分析Dalvik虛擬機器在Zygote程序中的啟動過程。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
Zygote程序在啟動的過程中,除了會建立一個Dalvik虛擬機器例項之外,還會將Java執行時庫載入到程序中來,以及註冊一些Android核心類的JNI方法來前面建立的Dalvik虛擬機器例項中去。注意,一個應用程式程序被Zygote程序孵化出來的時候,不僅會獲得Zygote程序中的Dalvik虛擬機器例項拷貝,還會與Zygote一起共享Java執行時庫,這完全得益於Linux核心的程序建立機制(fork)。這種Zygote孵化機制的優點是不僅可以快速地啟動一個應用程式程序,還可以節省整體的記憶體消耗,缺點是會影響開機速度,畢竟Zygote是在開機過程中啟動的。不過,總體來說,是利大於弊的,畢竟整個系統只有一個Zygote程序,而可能有無數個應用程式程序,而且我們不會經常去關閉手機,大多數情況下只是讓它進入休眠狀態。
從前面Android系統程序Zygote啟動過程的原始碼分析一文可以知道,Zygote程序在啟動的過程中,會呼叫到AndroidRuntime類的成員函式start,接下來我們就這個函式開始分析Dalvik虛擬機器啟動相關的過程,如圖1所示:
圖1 Dalvik虛擬機器的啟動過程
這個過程可以分為8個步驟,接下來我們就詳細分析每一個步驟。
Step 1. AndroidRuntime.start
這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。void AndroidRuntime::start(const char* className, const bool startSystemServer) { ...... /* start the virtual machine */ if (startVm(&mJavaVM, &env) != 0) goto bail; /* * Register android functions. */ if (startReg(env) < 0) { LOGE("Unable to register all android natives\n"); goto bail; } ...... /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ jclass startClass; jmethodID startMeth; slashClassName = strdup(className); for (cp = slashClassName; *cp != '\0'; cp++) if (*cp == '.') *cp = '/'; startClass = env->FindClass(slashClassName); if (startClass == NULL) { LOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { LOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); ...... } } LOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) LOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) LOGW("Warning: VM did not shut down cleanly\n"); ...... }
AndroidRuntime類的成員函式start主要做了以下四件事情:
1. 呼叫成員函式startVm來建立一個Dalvik虛擬機器例項,並且儲存在成員變數mJavaVM中。
2. 呼叫成員函式startReg來註冊一些Android核心類的JNI方法。
3. 呼叫引數className所描述的一個Java類的靜態成員函式main,來作為Zygote程序的Java層入口。從前面Android系統程序Zygote啟動過程的原始碼分析
4. 從com.android.internal.os.ZygoteInit類的靜態成員函式main返回來的時候,就說明Zygote程序準備要退出來了。在退出之前,會呼叫前面建立的Dalvik虛擬機器例項的成員函式DetachCurrentThread和DestroyJavaVM。其中,前者用來將Zygote程序的主執行緒脫離前面建立的Dalvik虛擬機器例項,後者是用來銷燬前面建立的Dalvik虛擬機器例項。
接下來,我們就主要關注Dalvik虛擬機器例項的建立過程,以及Android核心類JNI方法的註冊過程,即AndroidRuntime類的成員函式startVm和startReg的實現。
Step 2. AndroidRuntime.startVm
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
int result = -1;
JavaVMInitArgs initArgs;
JavaVMOption opt;
......
property_get("dalvik.vm.checkjni", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
checkJni = true;
} else if (strcmp(propBuf, "false") != 0) {
/* property is neither true nor false; fall back on kernel parameter */
property_get("ro.kernel.android.checkjni", propBuf, "");
if (propBuf[0] == '1') {
checkJni = true;
}
}
......
property_get("dalvik.vm.execution-mode", propBuf, "");
if (strcmp(propBuf, "int:portable") == 0) {
executionMode = kEMIntPortable;
} else if (strcmp(propBuf, "int:fast") == 0) {
executionMode = kEMIntFast;
#if defined(WITH_JIT)
} else if (strcmp(propBuf, "int:jit") == 0) {
executionMode = kEMJitCompiler;
#endif
}
property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, "");
......
strcpy(heapsizeOptsBuf, "-Xmx");
property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
//LOGI("Heap size: %s", heapsizeOptsBuf);
opt.optionString = heapsizeOptsBuf;
mOptions.add(opt);
......
if (checkJni) {
/* extended JNI checking */
opt.optionString = "-Xcheck:jni";
mOptions.add(opt);
......
}
......
if (executionMode == kEMIntPortable) {
opt.optionString = "-Xint:portable";
mOptions.add(opt);
} else if (executionMode == kEMIntFast) {
opt.optionString = "-Xint:fast";
mOptions.add(opt);
#if defined(WITH_JIT)
} else if (executionMode == kEMJitCompiler) {
opt.optionString = "-Xint:jit";
mOptions.add(opt);
#endif
}
......
if (stackTraceFileBuf[0] != '\0') {
static const char* stfOptName = "-Xstacktracefile:";
stackTraceFile = (char*) malloc(strlen(stfOptName) +
strlen(stackTraceFileBuf) +1);
strcpy(stackTraceFile, stfOptName);
strcat(stackTraceFile, stackTraceFileBuf);
opt.optionString = stackTraceFile;
mOptions.add(opt);
}
......
initArgs.options = mOptions.editArray();
initArgs.nOptions = mOptions.size();
......
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
LOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
result = 0;
bail:
free(stackTraceFile);
return result;
}
這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。
在啟動Dalvik虛擬機器的時候,可以指定一系列的選項,這些選項可以通過特定的系統屬性來指定。下面我們就簡單瞭解幾個可能有用的選項。
1. -Xcheck:jni:用來啟動JNI方法檢查。我們在C/C++程式碼中,可以修改Java物件的成員變數或者呼叫Java物件的成員函式。加了-Xcheck:jni選項之後,就可以對要訪問的Java物件的成員變數或者成員函式進行合法性檢查,例如,檢查型別是否匹配。我們可以通過dalvik.vm.checkjni或者ro.kernel.android.checkjni這兩個系統屬性來指定是否要啟用-Xcheck:jni選項。注意,加了-Xcheck:jni選項之後,會使用得JNI方法執行變慢。
2. -Xint:portable,-Xint:fast,-Xint:jit:用來指定Dalvik虛擬機器的執行模式。Dalvik虛擬機器支援三種執行模式,分別是Portable、Fast和Jit。Portable是指Dalvik虛擬機器以可移植的方式來進行編譯,也就是說,編譯出來的虛擬機器可以在任意平臺上執行。Fast是針對當前平臺對Dalvik虛擬機器進行編譯,這樣編譯出來的Dalvik虛擬機器可以進行特殊的優化,從而使得它能更快地執行程式。Jit不是解釋執行程式碼,而是將程式碼動態編譯成本地語言後再執行。我們可以通過dalvik.vm.execution-mode系統屬笥來指定Dalvik虛擬機器的解釋模式。
3. -Xstacktracefile:用來指定呼叫堆疊輸出檔案。Dalvik虛擬機器接收到SIGQUIT(Ctrl-\或者kill -3)訊號之後,會將所有執行緒的呼叫堆疊輸出來,預設是輸出到日誌裡面。指定了-Xstacktracefile選項之後,就可以將執行緒的呼叫堆疊輸出到指定的檔案中去。我們可以通過dalvik.vm.stack-trace-file系統屬性來指定呼叫堆疊輸出檔案。
4. -Xmx:用來指定Java物件堆的最大值。Dalvik虛擬機器的Java物件堆的預設最大值是16M,不過我們可以通過dalvik.vm.heapsize系統屬性來指定為其它值。
設定好Dalvik虛擬機器的啟動選項之後,AndroidRuntime的成員函式startVm就會呼叫另外一個函式JNI_CreateJavaVM來建立以及初始化一個Dalvik虛擬機器例項。
Step 3. JNI_CreateJavaVM
/*
* Create a new VM instance.
*
* The current thread becomes the main VM thread. We return immediately,
* which effectively means the caller is executing in a native method.
*/
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args)
{
const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
JNIEnvExt* pEnv = NULL;
JavaVMExt* pVM = NULL;
const char** argv;
int argc = 0;
......
/* zero globals; not strictly necessary the first time a VM is started */
memset(&gDvm, 0, sizeof(gDvm));
/*
* Set up structures for JNIEnv and VM.
*/
//pEnv = (JNIEnvExt*) malloc(sizeof(JNIEnvExt));
pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
memset(pVM, 0, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = pEnv;
......
argv = (const char**) malloc(sizeof(char*) * (args->nOptions));
memset(argv, 0, sizeof(char*) * (args->nOptions));
......
/*
* Convert JNI args to argv.
*
* We have to pull out vfprintf/exit/abort, because they use the
* "extraInfo" field to pass function pointer "hooks" in. We also
* look for the -Xcheck:jni stuff here.
*/
for (i = 0; i < args->nOptions; i++) {
......
}
......
/* set this up before initializing VM, so it can create some JNIEnvs */
gDvm.vmList = (JavaVM*) pVM;
/*
* Create an env for main thread. We need to have something set up
* here because some of the class initialization we do when starting
* up the VM will call into native code.
*/
pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
/* initialize VM */
gDvm.initializing = true;
if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) {
free(pEnv);
free(pVM);
goto bail;
}
/*
* Success! Return stuff to caller.
*/
dvmChangeStatus(NULL, THREAD_NATIVE);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
result = JNI_OK;
bail:
gDvm.initializing = false;
if (result == JNI_OK)
LOGV("JNI_CreateJavaVM succeeded\n");
else
LOGW("JNI_CreateJavaVM failed\n");
free(argv);
return result;
}
這個函式定義在檔案dalvik/vm/Jni.c中。JNI_CreateJavaVM主要完成以下四件事情。
1. 為當前程序建立一個Dalvik虛擬機器例項,即一個JavaVMExt物件。
2. 為當前執行緒建立和初始化一個JNI環境,即一個JNIEnvExt物件,這是通過呼叫函式dvmCreateJNIEnv來完成的。
3. 將引數vm_args所描述的Dalvik虛擬機器啟動選項拷貝到變數argv所描述的一個字串陣列中去,並且呼叫函式dvmStartup來初始化前面所建立的Dalvik虛擬機器例項。
4. 呼叫函式dvmChangeStatus將當前執行緒的狀態設定為正在執行NATIVE程式碼,並且將面所建立和初始化好的JavaVMExt物件和JNIEnvExt物件通過輸出引數p_vm和p_env返回給呼叫者。
gDvm是一個型別為DvmGlobals的全域性變數,用來收集當前程序所有虛擬機器相關的資訊,其中,它的成員變數vmList指向的就是當前程序中的Dalvik虛擬機器例項,即一個JavaVMExt物件。以後每當需要訪問當前程序中的Dalvik虛擬機器例項時,就可以通過全域性變數gDvm的成員變數vmList來獲得,避免了在函式之間傳遞該Dalvik虛擬機器例項。
每一個Dalvik虛擬機器例項都有一個函式表,儲存在對應的JavaVMExt物件的成員變數funcTable中,而這個函式表又被指定為gInvokeInterface。gInvokeInterface是一個型別為JNIInvokeInterface的結構體,它定義在檔案dalvik/vm/Jni.c中,如下所示:
static const struct JNIInvokeInterface gInvokeInterface = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon,
};
有了這個Dalvik虛擬機器函式表之後,我們就可以將當前執行緒Attach或者Detach到Dalvik虛擬機器中去,或者銷燬當前程序的Dalvik虛擬機器等。每一個Dalvik虛擬機器例項還有一個JNI環境列表,儲存在對應的JavaVMExt物件的成員變數envList中。注意,JavaVMExt物件的成員變數envList描述的是一個JNIEnvExt列表,其中,每一個Attach到Dalvik虛擬機器中去的執行緒都有一個對應的JNIEnvExt,用來描述它的JNI環境。有了這個JNI環境之後,我們才可以在Java函式和C/C++函式之間互相呼叫。
每一個JNIEnvExt物件都有兩個成員變數prev和next,它們均是一個JNIEnvExt指標,分別指向前一個JNIEnvExt物件和後一個JNIEnvExt物件,也就是說,每一個Dalvik虛擬機器例項的成員變數envList描述的是一個雙向JNIEnvExt列表,其中,列表中的第一個JNIEnvExt物件描述的是主執行緒的JNI環境。
上述提到的DvmGlobals結構體定義檔案dalvik/vm/Globals.h中,JNIInvokeInterface結構體定義在檔案dalvik/libnativehelper/include/nativehelper/jni.h中,JavaVMExt和JNIEnvExt結構體定義在檔案dalvik/vm/JniInternal.h中。
接下來,我們接下來就繼續分析函式dvmCreateJNIEnv和函式dvmStartup的實現,以便可以瞭解JNI環境的建立和初始化過程,以及Dalvik虛擬機器的虛擬機器初始化過程。
Step 4. dvmCreateJNIEnv
/*
* Create a new JNIEnv struct and add it to the VM's list.
*
* "self" will be NULL for the main thread, since the VM hasn't started
* yet; the value will be filled in later.
*/
JNIEnv* dvmCreateJNIEnv(Thread* self)
{
JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
JNIEnvExt* newEnv;
......
newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
newEnv->funcTable = &gNativeInterface;
newEnv->vm = vm;
......
if (self != NULL) {
dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);
assert(newEnv->envThreadId != 0);
} else {
/* make it obvious if we fail to initialize these later */
newEnv->envThreadId = 0x77777775;
newEnv->self = (Thread*) 0x77777779;
}
......
/* insert at head of list */
newEnv->next = vm->envList;
assert(newEnv->prev == NULL);
if (vm->envList == NULL) // rare, but possible
vm->envList = newEnv;
else
vm->envList->prev = newEnv;
vm->envList = newEnv;
......
return (JNIEnv*) newEnv;
}
這個函式定義在檔案dalvik/vm/Jni.c中。函式dvmCreateJNIEnv主要是執行了以下三個操作:
1. 建立一個JNIEnvExt物件,用來描述一個JNI環境,並且設定這個JNIEnvExt物件的宿主Dalvik虛擬機器,以及所使用的本地介面表,即設定這個JNIEnvExt物件的成員變數funcTable和vm。這裡的宿主Dalvik虛擬機器即為當前程序的Dalvik虛擬機器,它儲存在全域性變數gDvm的成員變數vmList中。本地介面表由全域性變數gNativeInterface來描述。
2. 引數self描述的是前面建立的JNIEnvExt物件要關聯的執行緒,可以通過呼叫函式dvmSetJniEnvThreadId來將它們關聯起來。注意,當引數self的值等於NULL的時候,就表示前面的JNIEnvExt物件是要與主執行緒關聯的,但是要等到後面再關聯,因為現在用來描述主執行緒的Thread物件還沒有準備好。通過將一個JNIEnvExt物件的成員變數envThreadId和self的值分別設定為0x77777775和0x77777779來表示它還沒有與執行緒關聯。
3. 在一個Dalvik虛擬機器裡面,可以執行多個執行緒。所有關聯有JNI環境的執行緒都有一個對應的JNIEnvExt物件,這些JNIEnvExt物件相互連線在一起儲存在用來描述其宿主Dalvik虛擬機器的一個JavaVMExt物件的成員變數envList中。因此,前面建立的JNIEnvExt物件需要連線到其宿主Dalvik虛擬機器的JavaVMExt連結串列中去。
gNativeInterface是一個型別為JNINativeInterface的結構體,它定義在檔案dalvik/vm/Jni.c,如下所示:
static const struct JNINativeInterface gNativeInterface = {
......
FindClass,
......
GetMethodID,
......
CallObjectMethod,
......
GetFieldID,
......
SetIntField,
......
RegisterNatives,
UnregisterNatives,
......
GetJavaVM,
......
};
從gNativeInterface的定義就可以看出,結構體JNINativeInterface用來描述一個本地介面表。當我們需要在C/C++程式碼在中呼叫Java函式,就要用到這個本地介面表,例如:1. 呼叫函式FindClass可以找到指定的Java類;
2. 呼叫函式GetMethodID可以獲得一個Java類的成員函式,並且可以通過類似CallObjectMethod函式來間接呼叫它;
3. 呼叫函式GetFieldID可以獲得一個Java類的成員變數,並且可以通過類似SetIntField的函式來設定它的值;
4. 呼叫函式RegisterNatives和UnregisterNatives可以註冊和反註冊JNI方法到一個Java類中去,以便可以在Java函式中呼叫;
5. 呼叫函式GetJavaVM可以獲得當前程序中的Dalvik虛擬機器例項。
事實上,結構體JNINativeInterface定義的可以在C/C++程式碼中呼叫的函式非常多,具體可以參考它在dalvik\libnativehelper\include\nativehelper\jni.h檔案中的定義。
這一步執行完成之後,返回到前面的Step 3中,即函式JNI_CreateJavaVM中,接下來就會繼續呼叫函式dvmStartup來初始化前面所建立的Dalvik虛擬機器例項。
Step 5. dvmStartup
這個函式定義在檔案dalvik/vm/Init.c中,用來初始化Dalvik虛擬機器,我們分段來閱讀:
/*
* VM initialization. Pass in any options provided on the command line.
* Do not pass in the class name or the options for the class.
*
* Returns 0 on success.
*/
int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
JNIEnv* pEnv)
{
int i, cc;
......
setCommandLineDefaults();
/* prep properties storage */
if (!dvmPropertiesStartup(argc))
goto fail;
/*
* Process the option flags (if any).
*/
cc = dvmProcessOptions(argc, argv, ignoreUnrecognized);
if (cc != 0) {
......
goto fail;
}
這段程式碼用來處理Dalvik虛擬機器的啟動選項,這些啟動選項儲存在引數argv中,並且個數等於argc。在處理這些啟動選項之前,還會執行以下兩個操作:1. 呼叫函式setCommandLineDefaults來給Dalvik虛擬機器設定預設引數,因為啟動選項不一定會指定Dalvik虛擬機器的所有屬性。
2. 呼叫函式dvmPropertiesStartup來分配足夠的記憶體空間來容納由引數argv和argc所描述的啟動選項。
完成以上兩個操作之後,就可以呼叫函式dvmProcessOptions來處理引數argv和argc所描述的啟動選項了,也就是根據這些選項值來設定Dalvik虛擬機器的屬性,例如,設定Dalvik虛擬機器的Java物件堆的最大值。
在上述程式碼中,函式setCommandLineDefaults和dvmPropertiesStartup定義在檔案dalvik/vm/Init.c中,函式dvmPropertiesStartup定義在檔案dalvik/vm/Properties.c中。
我們繼續往下閱讀程式碼:
/* configure signal handling */
if (!gDvm.reduceSignals)
blockSignals();
如果我們沒有在Dalvik虛擬機器的啟動選項中指定-Xrs,那麼gDvm.reduceSignals的值就會被設定為false,表示要在當前執行緒中遮蔽掉SIGQUIT訊號。在這種情況下,會有一個執行緒專門用來處理SIGQUIT訊號。這個執行緒在接收到SIGQUIT訊號的時候,就會將各個執行緒的呼叫堆疊打印出來,因此,這個執行緒又稱為dump-stack-trace執行緒。遮蔽當前執行緒的SIGQUIT訊號是通過呼叫函式blockSignals來實現的,這個函式定義在檔案dalvik/vm/Init.c中。
我們繼續往下閱讀程式碼:
/*
* Initialize components.
*/
if (!dvmAllocTrackerStartup())
goto fail;
if (!dvmGcStartup())
goto fail;
if (!dvmThreadStartup())
goto fail;
if (!dvmInlineNativeStartup())
goto fail;
if (!dvmVerificationStartup())
goto fail;
if (!dvmRegisterMapStartup())
goto fail;
if (!dvmInstanceofStartup())
goto fail;
if (!dvmClassStartup())
goto fail;
if (!dvmThreadObjStartup())
goto fail;
if (!dvmExceptionStartup())
goto fail;
if (!dvmStringInternStartup())
goto fail;
if (!dvmNativeStartup())
goto fail;
if (!dvmInternalNativeStartup())
goto fail;
if (!dvmJniStartup())
goto fail;
if (!dvmReflectStartup())
goto fail;
if (!dvmProfilingStartup())
goto fail;
這段程式碼用來初始化Dalvik虛擬機器的各個子模組,接下來我們就分別描述。1. dvmAllocTrackerStartup
這個函式定義在檔案dalvik/vm/AllocTracker.c中,用來初始化Davlik虛擬機器的物件分配記錄子模組,這樣我們就可以通過DDMS工具來檢視Davlik虛擬機器的物件分配情況。
2. dvmGcStartup
這個函式定義在檔案dalvik/vm/alloc/Alloc.c中,用來初始化Davlik虛擬機器的垃圾收集( GC)子模組。
3. dvmThreadStartup
這個函式定義在檔案dalvik/vm/Thread.c中,用來初始化Davlik虛擬機器的執行緒列表、為主執行緒建立一個Thread物件以及為主執行緒初始化執行環境。Davlik虛擬機器中的所有執行緒均是本地作業系統執行緒。在Linux系統中,一般都是使用pthread庫來建立和管理執行緒的,Android系統也不例外,也就是說,Davlik虛擬機器中的每一個執行緒均是一個pthread執行緒。注意,Davlik虛擬機器中的每一個執行緒均用一個Thread結構體來描述,這些Thread結構體組織在一個列表中,因此,這裡要先對它進行初始化。
4. dvmInlineNativeStartup
這個函式定義在檔案dalvik/vm/InlineNative.c中,用來初始化Davlik虛擬機器的內建Native函式表。這些內建Native函式主要是針對java.Lang.String、java.Lang.Math、java.Lang.Float和java.Lang.Double類的,用來替換這些類的某些成員函式原來的實現(包括Java實現和Native實現)。例如,當我們呼叫java.Lang.String類的成員函式compareTo來比較兩個字串的大小時,實際執行的是由Davlik虛擬機器提供的內建函式javaLangString_compareTo(同樣是定義在檔案dalvik/vm/InlineNative.c中)。在提供有__memcmp16函式的系統中,函式javaLangString_compareTo會利用它來直接比較兩個字串的大小。由於函式__memcmp16是用優化過的組合語言的來實現的,它的效率會更高。
5. dvmVerificationStartup
這個函式定義在檔案dalvik/vm/analysis/DexVerify.c中,用來初始化Dex檔案驗證器。Davlik虛擬機器與Java虛擬機器一樣,在載入一個類檔案的時候,一般需要驗證它的合法性,也就是驗證檔案中有沒有非法的指令或者操作等。
6. dvmRegisterMapStartup
這個函式定義在檔案dalvik/vm/analysis/RegisterMap.c中,用來初始化暫存器對映集(Register Map)子模組。Davlik虛擬機器支援精確垃圾收集(Exact GC或者Precise GC),也就是說,在進行垃圾收集的時候,Davlik虛擬機器可以準確地判斷當前正在使用的每一個暫存器裡面儲存的是物件引用還是非物件引用。對於物件引用,意味被引用的物件現在還不可以回收,因此,就可以進行精確的垃圾收集。
為了幫助垃圾收集器準備地判斷暫存器儲存的是物件引用還是非物件引用,Davlik虛擬機器在驗證了一個類之後,還會為它的每一個成員函式生成一個暫存器對映集。暫存器對映集記錄了類成員函式在每一個GC安全點(Safe Point)中的暫存器使用情況,也就是記錄每一個暫存器裡面儲存的是物件引用還是非物件引用。由於垃圾收集器一定是在GC安全點進行垃圾收集的,因此,根據每一個GC安全點的暫存器對映集,就可以準確地知道物件的引用情況,從而可以確定哪些可以回收,哪些物件還不可以回收。
7. dvmInstanceofStartup
這個函式定義在檔案dalvik/vm/oo/TypeCheck.c中,用來初始化instanceof操作符子模組。在使用instanceof操作符來判斷一個物件A是否是一個類B的例項時,Davlik虛擬機器需要檢查類B是否是從物件A的宣告類繼承下來的。由於這個檢查的過程比較耗時,Davlik虛擬機器在內部使用一個緩衝,用來記錄第一次兩個類之間的instanceof操作結果,這樣後面再碰到相同的instanceof操作時,就可以快速地得到結果。
8. dvmClassStartup
這個函式定義在檔案dalvik/vm/oo/Class.c中,用來初始化啟動類載入器(Bootstrap Class Loader),同時還會初始化java.lang.Class類。啟動類載入器是用來載入Java核心類的,用來保證安全性,即保證載入的Java核心類是合法的。
9. dvmThreadObjStartup
這個函式定義在檔案dalvik/vm/Thread.c中,用來載入與執行緒相關的類,即java.lang.Thread、java.lang.VMThread和java.lang.ThreadGroup。
10. dvmExceptionStartup
這個函式定義在檔案dalvik/vm/Exception.c中,用來載入與異常相關的類,即java.lang.Throwable、java.lang.RuntimeException、java.lang.StackOverflowError、java.lang.Error、java.lang.StackTraceElement和java.lang.StackTraceElement類。
11. dvmStringInternStartup
這個函式定義在檔案dalvik/vm/Intern.c中,用來初始化java.lang.String類內部私有一個字串池,這樣當Dalvik虛擬機器執行起來之後,我們就可以呼叫java.lang.String類的成員函式intern來訪問這個字串池裡面的字串。
12. dvmNativeStartup
這個函式定義在檔案dalvik/vm/Native.c中,用來初始化Native Shared Object庫載入表,也就是SO庫載入表。這個載入表是用來描述當前程序有哪些SO檔案已經被載入過了。
13. dvmInternalNativeStartup
這個函式定義在檔案dalvik/vm/native/InternalNative.c中,用來初始化一個內部Native函式表。所有需要直接訪問Dalvik虛擬機器內部函式或者資料結構的Native函式都定義在這張表中,因為它們如果定義在外部的其它SO檔案中,就無法直接訪問Dalvik虛擬機器的內部函式或者資料結構。例如,前面提到的java.lang.String類的成員函式intent,由於它要訪問Dalvik虛擬機器內部的一個私有字串池,因此,它所對應的Native函式就要在Dalvik虛擬機器內部實現。
14. dvmJniStartup
這個函式定義在檔案dalvik/vm/Jni.c中,用來初始化全域性引用表,以及載入一些與Direct Buffer相關的類,如DirectBuffer、PhantomReference和ReferenceQueue等。
我們在一個JNI方法中,可能會需要訪問一些Java物件,這樣就需要通知GC,這些Java物件現在正在被Native Code引用,不能回收。這些被Native Code引用的Java物件就會被記錄在一個全域性引用表中,具體的做法就是呼叫JNI環境物件(JNIEnv)的成員函式NewLocalRef/DeleteLocalRef和NewGlobalRef/DeleteGlobalRef等來顯式地引用或者釋放Java物件。
有時候我們需要在Java程式碼中,直接在Native層分配記憶體,也就直接使用malloc來分配記憶體。這些Native記憶體不同於在Java堆中分配的記憶體,區別在於前者需要不接受GC管理,而後者接受GC管理。這些直接在Native層分配的記憶體有什麼用呢?考慮一個場景,我們需要在Java程式碼中從一個IO裝置中讀取資料。從IO裝置讀取資料意味著要呼叫由本地作業系統提供的read介面來實現。這樣我們就有兩種做法。第一種做法在Native層臨時分配一個緩衝區,用來儲存從IO裝置read回來的資料,然後再將這個資料拷貝到Java層中去,也就是拷貝到Java堆去使用。第二種做法是在Java層建立一個物件,這個物件在Native層直接關聯有一塊記憶體,從IO裝置read回來的資料就直接儲存這塊記憶體中。第二種方法和第一種方法相比,減少了一次記憶體拷貝,因而可以提高效能。
我們將這種能夠直接在Native層中分配記憶體的Java物件就稱為DirectBuffer。由於DirectBuffer使用的記憶體是不接受GC管理的,因此,我們就需要通過其它的方式來管理它們。具體做法就是為每一個DirectBuffer物件建立一個PhantomReference引用。注意,DirectBuffer物件本身是一個Java物件,它是接受GC管理的。當GC準備回收一個DirectBuffer物件時,如果發現它還有PhantomReference引用,那就會在回收它之前,把相應的PhantomReference引用加入到與之關聯的一個ReferenceQueue佇列中去。這樣我們就可以通過判斷一個DirectBuffer物件的PhantomReference引用是否已經加入到一個相關的ReferenceQueue佇列中。如果已經加入了的話,那麼就可以在該DirectBuffer物件被回收之前,釋放掉之前為它在Native層分配的記憶體。
15. dvmReflectStartup
這個函式定義在檔案dalvik/vm/reflect/Reflect.c中,用來載入反射相關的類,如java.lang.reflect.AccessibleObject、java.lang.reflect.Constructor、java.lang.reflect.Field、java.lang.reflect.Method和java.lang.reflect.Proxy等。
16. dvmProfilingStartup
這個函式定義在檔案dalvik/vm/Profile.c,用來初始化Dalvik虛擬機器的效能分析子模組,以及載入dalvik.system.VMDebug類等。
Dalvik虛擬機器的各個子模組初始化完成之後,我們繼續往下閱讀程式碼:
/* make sure we got these [can this go away?] */
assert(gDvm.classJavaLangClass != NULL);
assert(gDvm.classJavaLangObject != NULL);
//assert(gDvm.classJavaLangString != NULL);
assert(gDvm.classJavaLangThread != NULL);
assert(gDvm.classJavaLangVMThread != NULL);
assert(gDvm.classJavaLangThreadGroup != NULL);
/*
* Make sure these exist. If they don't, we can return a failure out
* of main and nip the whole thing in the bud.
*/
static const char* earlyClasses[] = {
"Ljava/lang/InternalError;",
"Ljava/lang/StackOverflowError;",
"Ljava/lang/UnsatisfiedLinkError;",
"Ljava/lang/NoClassDefFoundError;",
NULL
};
const char** pClassName;
for (pClassName = earlyClasses; *pClassName != NULL; pClassName++) {
if (dvmFindSystemClassNoInit(*pClassName) == NULL)
goto fail;
}
這段程式碼檢查java.lang.Class、java.lang.Object、java.lang.Thread、java.lang.VMThread和java.lang.ThreadGroup這五個核心類經過前面的初始化操作後已經得到載入,並且確保系統中存在java.lang.InternalError、java.lang.StackOverflowError、java.lang.UnsatisfiedLinkError和java.lang.NoClassDefFoundError這四個核心類。我們繼續往下閱讀程式碼:
/*
* Miscellaneous class library validation.
*/
if (!dvmValidateBoxClasses())
goto fail;
/*
* Do the last bits of Thread struct initialization we need to allow
* JNI calls to work.
*/
if (!dvmPrepMainForJni(pEnv))
goto fail;
/*
* Register the system native methods, which are registered through JNI.
*/
if (!registerSystemNatives(pEnv))
goto fail;
/*
* Do some "late" initialization for the memory allocator. This may
* allocate storage and initialize classes.
*/
if (!dvmCreateStockExceptions())
goto fail;
/*
* At this point, the VM is in a pretty good state. Finish prep on
* the main thread (specifically, create a java.lang.Thread object to go
* along with our Thread struct). Note we will probably be executing
* some interpreted class initializer code in here.
*/
if (!dvmPrepMainThread())
goto fail;
/*
* Make sure we haven't accumulated any tracked references. The main
* thread should be starting with a clean slate.
*/
if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
{
LOGW("Warning: tracked references remain post-initialization\n");
dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
}
/* general debugging setup */
if (!dvmDebuggerStartup())
goto fail;
這段程式碼繼續執行其它函式來執行其它的初始化和檢查工作,如下所示:1. dvmValidateBoxClasses
這個函式定義在檔案dalvik/vm/reflect/Reflect.c中,用來驗證Dalvik虛擬機器中存在相應的裝箱類,並且這些裝箱類有且僅有一個成員變數,這個成員變數是用來描述對應的數字值的。這些裝箱類包括java.lang.Boolean、java.lang.Character、java.lang.Float、java.lang.Double、java.lang.Byte、java.lang.Short、java.lang.Integer和java.lang.Long。
所謂裝箱,就是可以自動將一個數值轉換一個物件,例如,將數字1自動轉換為一個java.lang.Integer物件。相應地,也要求能將一個裝箱類物件轉換成一個數字,例如,將一個值等於1的java.lang.Integer物件轉換為數字1。
2. dvmPrepMainForJni
這個函式定義在檔案dalvik/vm/Thread.c中,用來準備主執行緒的JNI環境,即將在前面的Step 5中為主執行緒建立的Thread物件與在前面Step 4中建立的JNI環境關聯起來。回憶在前面的Step 4中,雖然我們已經為當前執行緒建立好一個JNI環境了,但是還沒有將該JNI環境與主執行緒關聯,也就是還沒有將主執行緒的ID設定到該JNI環境中去。
3. registerSystemNatives
這個函式定義在檔案dalvik/vm/Init.c中,它呼叫另外一個函式jniRegisterSystemMethods,後者接著又呼叫了函式registerCoreLibrariesJni來為Java核心類註冊JNI方法。函式registerCoreLibrariesJni定義在檔案libcore/luni/src/main/native/Register.cpp中。
4. dvmCreateStockExceptions
這個函式定義在檔案dalvik/vm/alloc/Alloc.c中,用來預建立一些與記憶體分配有關的異常物件,並且將它們快取起來,以便以後可以快速使用。這些異常物件包括java.lang.OutOfMemoryError、java.lang.InternalError和java.lang.NoClassDefFoundError。
5. dvmPrepMainThread
這個函式定義在檔案dalvik/vm/Thread.c中,用來為主執行緒建立一個java.lang.ThreadGroup物件、一個java.lang.Thread對角和java.lang.VMThread物件。這些Java物件和在前面Step 5中建立的C++層Thread物件關聯一起,共同用來描述Dalvik虛擬機器的主執行緒。
6. dvmReferenceTableEntries
這個函式定義在檔案dalvik/vm/ReferenceTable.h中,用來確保主執行緒當前不引用有任何Java物件,這是為了保證主執行緒接下來以乾淨的方式來執行程式入口。
7. dvmDebuggerStartup
這個函式定義在檔案dalvik/vm/Debugger.c中,用來初始化Dalvik虛擬機器的除錯環境。注意,Dalvik虛擬機器與Java虛擬機器一樣,都是通過JDWP協議來支援遠端除錯的。
上述初始化和檢查操作執行完成之後,我們再來看最後一段程式碼:
/*
* Init for either zygote mode or non-zygote mode. The key difference
* is that we don't start any additional threads in Zygote mode.
*/
if (gDvm.zygote) {
if (!dvmInitZygote())
goto fail;
} else {
if (!dvmInitAfterZygote())
goto fail;
}
......
return 0;
fail:
dvmShutdown();
return 1;
}
這段程式碼完成Dalvik虛擬機器的最後一步初始化工作。它檢查Dalvik虛擬機器是否指定了-Xzygote啟動選項。如果指定了的話,那麼就說明當前是在Zygote程序中啟動Dalvik虛擬機器,因此,接下來就會呼叫函式dvmInitZygote來執行最後一步初始化工作。否則的話,就會呼叫另外一個函式dvmInitAfterZygote來執行最後一步初始化工作。由於當前是在Zygote程序中啟動Dalvik虛擬機器的,因此,接下來我們就繼續分析函式dvmInitZygote的實現。在接下來的文章中分析Android應用程式程序的建立過程時,我們再分析函式dvmInitAfterZygote的實現。
Step 6. dvmInitZygote
/*
* Do zygote-mode-only initialization.
*/
static bool dvmInitZygote(void)
{
/* zygote goes into its own process group */
setpgid(0,0);
return true;
}
這個函式定義在檔案dalvik/vm/Init.c中。函式dvmInitZygote的實現很簡單,它只是呼叫了系統呼叫setpgid來設定當前程序,即Zygote程序的程序組ID。注意,在呼叫setpgid的時候,傳遞進去的兩個引數均為0,這意味著Zygote程序的程序組ID與程序ID是相同的,也就是說,Zygote程序執行在一個單獨的程序組裡面。
這一步執行完成之後,Dalvik虛擬機器的建立和初始化工作就完成了,回到前面的Step 1中,即AndroidRuntime類的成員函式start中,接下來就會呼叫AndroidRuntime類的另外一個成員函式startReg來註冊Android核心類的JNI方法。
Step 7. AndroidRuntime.startReg
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
LOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven't really
* started the VM yet, they're all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。AndroidRuntime類的成員函式startReg首先呼叫函式androidSetCreateThreadFunc來設定一個執行緒建立鉤子javaCreateThreadEtc。這個執行緒建立鉤子是用來初始化一個Native執行緒的JNI環境的,也就是說,當我們在C++程式碼中建立一個Native執行緒的時候,函式javaCreateThreadEtc會被呼叫來初始化該Native執行緒的JNI環境。後面在分析Dalvik虛擬機器執行緒的建立過程時,我們再詳細分析函式javaCreateThreadEtc的實現。
AndroidRuntime類的成員函式startReg接著呼叫函式register_jni_procs來註冊Android核心類的JNI方法。在註冊JNI方法的過程中,需要在Native程式碼中引用到一些Java物件,這些Java物件引用需要記錄在當前執行緒的一個Native堆疊中。但是此時Dalvik虛擬機器還沒有真正執行起來,也就是當前執行緒的Native堆疊還沒有準備就緒。在這種情況下,就需要在註冊JNI方法之前,手動地將在當前執行緒的Native堆疊中壓入一個幀(Frame),並且在註冊JNI方法之後,手動地將該幀彈出來。
當前執行緒的JNI環境是由引數env所指向的一個JNIEnv物件來描述的,通過呼叫它的成員函式PushLocalFrame和PopLocalFrame就可以手動地往當前執行緒的Native堆疊壓入和彈出一個幀。注意,這個幀是一個本地幀,只可以用來儲存Java物件在Native程式碼中的本地引用。
函式register_jni_procs的實現如下所示:
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
return -1;
}
}
return 0;
}
這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。從前面的呼叫過程可以知道,引數array指向的是全域性變數gRegJNI所描述的一個JNI方法註冊函式表,其中,每一個表項都用一個RegJNIRec物件來描述,而每一個RegJNIRec物件都有一個成員變數mProc,指向一個JNI方法註冊函式。通過依次呼叫這些註冊函式,就可以將Android核心類的JNI方法註冊到前面的所建立的Dalvik虛擬機器中去。
通過觀察全域性變數gRegJNI所描述的JNI方法註冊函式表,我們就可以看出註冊了哪些Android核心類的JNI方法,如下所示:
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_debug_JNITest),
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_FloatMath),
REG_JNI(register_android_text_format_Time),
REG_JNI(register_android_pim_EventRecurrence),
REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
REG_JNI(register_android_content_XmlBlock),
REG_JNI(register_android_emoji_EmojiFactory),
REG_JNI(register_android_security_Md5MessageDigest),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_AndroidBidi),
REG_JNI(register_android_text_KeyCharacterMap),
REG_JNI(register_android_os_Process),
REG_JNI(register_android_os_Binder),
REG_JNI(register_android_view_Display),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_PixelFormat),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_ViewRoot),
REG_JNI(register_com_google_android_gles_jni_EGLImpl),
REG_JNI(register_com_google_android_gles_jni_GLImpl),
REG_JNI(register_android_opengl_jni_GLES10),
REG_JNI(register_android_opengl_jni_GLES10Ext),
REG_JNI(register_android_opengl_jni_GLES11),
REG_JNI(register_android_opengl_jni_GLES11Ext),
REG_JNI(register_android_opengl_jni_GLES20),
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
REG_JNI(register_android_graphics_BitmapRegionDecoder),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_LayerRasterizer),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
REG_JNI(register_android_graphics_Movie),
REG_JNI(register_android_graphics_NinePatch),
REG_JNI(register_android_graphics_Paint),
REG_JNI(register_android_graphics_Path),
REG_JNI(register_android_graphics_PathMeasure),
REG_JNI(register_android_graphics_PathEffect),
REG_JNI(register_android_graphics_Picture),
REG_JNI(register_android_graphics_PorterDuff),
REG_JNI(register_android_graphics_Rasterizer),
REG_JNI(register_android_graphics_Region),
REG_JNI(register_android_graphics_Shader),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_com_android_internal_graphics_NativeUtils),
REG_JNI(register_android_database_CursorWindow),
REG_JNI(register_android_database_SQLiteCompiledSql),
REG_JNI(register_android_database_SQLiteDatabase),
REG_JNI(register_android_database_SQLiteDebug),
REG_JNI(register_android_database_SQLiteProgram),
REG_JNI(register_android_database_SQLiteQuery),
REG_JNI(register_android_database_SQLiteStatement),
REG_JNI(register_android_os_Debug),
REG_JNI(register_android_os_FileObserver),
REG_JNI(register_android_os_FileUtils),
REG_JNI(register_android_os_MessageQueue),
REG_JNI(register_android_os_ParcelFileDescriptor),
REG_JNI(register_android_os_Power),
REG_JNI(register_android_os_StatFs),
REG_JNI(register_android_os_SystemProperties),
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_net_TrafficStats),
REG_JNI(register_android_net_wifi_WifiManager),
REG_JNI(register_android_nfc_NdefMessage),
REG_JNI(register_android_nfc_NdefRecord),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_media_AudioRecord),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioTrack),
REG_JNI(register_android_media_JetPlayer),
REG_JNI(register_android_media_ToneGenerator),
REG_JNI(register_android_opengl_classes),
REG_JNI(register_android_bluetooth_HeadsetBase),
REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
REG_JNI(register_android_bluetooth_BluetoothSocket),
REG_JNI(register_android_bluetooth_ScoSocket),
REG_JNI(register_android_server_BluetoothService),
REG_JNI(register_android_server_BluetoothEventLoop),
REG_JNI(register_android_server_BluetoothA2dpService),
REG_JNI(register_android_server_Watchdog),
REG_JNI(register_android_message_digest_sha1),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
REG_JNI(register_android_backup_FileBackupHelperBase),
REG_JNI(register_android_backup_BackupHelperDispatcher),
REG_JNI(register_android_app_NativeActivity),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputQueue),
REG_JNI(register_android_view_KeyEvent),
REG_JNI(register_android_view_MotionEvent),
REG_JNI(register_android_content_res_ObbScanner),
REG_JNI(register_android_content_res_Configuration),
};
上述函式表同樣是定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。回到AndroidRuntime類的成員函式startReg中,接下來我們就繼續分析函式androidSetCreateThreadFunc的實現,以便可以瞭解執行緒建立鉤子javaCreateThreadEtc的註冊過程。
Step 8. androidSetCreateThreadFunc
static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc;
......
void androidSetCreateThreadFunc(android_create_thread_fn func)
{
gCreateThreadFn = func;
}
這個函式定義在檔案frameworks/base/libs/utils/Threads.cpp中。從這裡就可以看到,執行緒建立鉤子javaCreateThreadEtc被儲存在一個函式指標gCreateThreadFn中。注意,函式指標gCreateThreadFn預設是指向函式androidCreateRawThreadEtc的,也就是說,如果我們不設定執行緒建立鉤子的話,函式androidCreateRawThreadEtc就是預設使用的執行緒建立函式。後面在分析Dalvik虛擬機器執行緒的建立過程時,我們再詳細分析函式指標gCreateThreadFn是如何使用的。
至此,我們就分析完成Dalvik虛擬機器在Zygote程序中的啟動過程,這個啟動過程主要就是完成了以下四個事情:
1. 建立了一個Dalvik虛擬機器例項;
2. 載入了Java核心類及其JNI方法;
3. 為主執行緒的設定了一個JNI環境;
4. 註冊了Android核心類的JNI方法。
換句話說,就是Zygote程序為Android系統準備好了一個Dalvik虛擬機器例項,以後Zygote程序在建立Android應用程式程序的時候,就可以將它自身的Dalvik虛擬機器例項複製到新建立Android應用程式程序中去,從而加快了Android應用程式程序的啟動過程。此外,Java核心類和Android核心類(位於dex檔案中),以及它們的JNI方法(位於so檔案中),都是以記憶體對映的方式來讀取的,因此,Zygote程序在建立Android應用程式程序的時候,除了可以將自身的Dalvik虛擬機器例項複製到新建立的Android應用程式程序之外,還可以與新建立的Android應用程式程序共享Java核心類和Android核心類,以及它們的JNI方法,這樣就可以節省記憶體消耗。
同時,我們也應該看到,Zygote程序為了加快Android應用程式程序的啟動過程,犧牲了自己的啟動速度,因為它需要載入大量的Java核心類,以及註冊大量的Android核心類JNI方法。Dalvik虛擬機器在載入Java核心類的時候,還需要對它們進行驗證以及優化,這些通常都是比較耗時的。又由於Zygote程序是由init程序啟動的,也就是說Zygote程序在是開機的時候進行啟動的,因此,Zygote程序的犧牲是比較大的。不過畢竟我們在玩手機的時候,很少會關機,也就是很少開機,因此,犧牲Zygote程序的啟動速度是值得的,換來的是Android應用程式的快速啟動。而且,Android系統為了加快Java類的載入速度,還會想方設法地提前對Dex檔案進行驗證和優化,這些措施具體參考Dalvik Optimization and Verification With dexopt一文。
學習了Dalvik虛擬機器的啟動過程之後,在接下來的一篇文章中,我們就繼續分析Dalvik虛擬機器的執行機制,敬請關注!