從Android 4.4版之後Google就引入ART(Android run time)讓使用者多一個執行app的環境選擇.在原本的DVM下, android app每次在執行時, 都需要dvm的直譯器将byte code轉成machine code, 即使在android 2.2之後加入了JIT 功能,在執行上依然還是會有效能的問題. Google為了要解決這個VM需要轉譯的捆擾問題,便引入了ART的技術.在ART的環境下, app在第一次安裝時就會将所有的byte code轉譯成machine code.讓java app完全變成一個真正的native app.這個過程就叫做預編譯(Ahead of time)簡稱AOT. ART程式代碼架構到底是長成甚麼樣?以下就先從ART的初始化過程做個研究記錄.
1. 起源
目前Android的預設引擎仍然是DVM, 是以ART仍屬于一個單獨的command 程式.需要由使用者在開機之後做切換.是以VM并非由AndroidRuntime去啟動.單獨的process就由main開始吧
dalvikvm.cc
int main(int argc, char** argv) {
return art::dalvikvm(argc, argv);
}
namespace art {
[...]
static int dalvikvm(int argc, char** argv) {
[...]
if (JNI_CreateJavaVM(&vm, &env, &init_args) != JNI_OK) {
fprintf(stderr, "Failed to initialize runtime (check log for details)\n");
return EXIT_FAILURE;
}
[...]
}
[...]
}
由art的定位跟dvm的定位是同等級的,是以在android的source code中擺的地方在同在根目錄下.是以在android 4.4的source code下會發現多一個art的檔案夾.這裡特别要說明的是裡面所有c++ 程式代碼的擴充名為.cc而不是.cpp, 這兩個擴充名都是代表c++程式代碼檔案.并沒甚麼太大的差異.至于為何突然換成.cc的擴充名.其中故事緣由隻有Google和天知道,況且也不是這次的研究重點.由上面的片段程式代碼會發現由main一直到JNI_CreateJavaVM函數都隻是單純的函數呼叫,沒甚麼特别的.但是有一點要提的就是這裡用了c++的空間機制将JNI_CreateJavaVM包起來,原因就是在Dalvik的程式架構中也有一個同名的JNI_CreateJavaVM 函數, 是以在art的程式架構中一定要用個名子空間包起來以防編譯程式發出同名錯誤的訊息.
2. 建立Virtual Machine
在原本的DVM下,建立vm做了以下事項: a. 設定 JNIEnv和VM的架構b. 為Main thread建立JNIEnvc. 初始化VM.哪在art下又是如何建立VM呢?
jni_internal.cc
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
[...]
if (!Runtime::Create(options, ignore_unrecognized)) {
return JNI_ERR;
}
[...]
bool started = runtime->Start();
[...]
}
看來在art的程式架構中,JNI_CreateJvavVM函數作的事又被Runtime這個類别抽象化了.并非如DVM的程式架構中,JNI_CreateJvavVM函數直接建立Jni以及初始化VM.感覺ART的程式設計更像C++的程式設計了,哪天會看到ART和DVM被Google利用c++的樣闆機制來重新打造也說不定.接下來就直接來看看Rumtime的create和start 函數做了甚麼?
3. 開創執行環境
閑話不多說,直接來看看程式代碼.
runtime.cc
bool Runtime::Create(const Options& options, bool ignore_unrecognized) {
[...]
instance_ = new Runtime;
if (!instance_->Init(options, ignore_unrecognized)) {
[...]
}
return true;
}
bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {
UniquePtr<ParsedOptions> options(ParsedOptions::Create(raw_options, ignore_unrecognized));
[...]
heap_ = new gc::Heap(options->heap_initial_size_,
options->heap_growth_limit_,
options->heap_min_free_,
options->heap_max_free_,
options->heap_target_utilization_,
options->heap_maximum_size_,
options->image_,
options->is_concurrent_gc_enabled_,
options->parallel_gc_threads_,
options->conc_gc_threads_,
options->low_memory_mode_,
options->long_pause_log_threshold_,
options->long_gc_log_threshold_,
options->ignore_max_footprint_);
[...]
java_vm_ = new JavaVMExt(this, options.get());
Thread::Startup();
Thread* self = Thread::Attach("main", false, NULL, false);
[...]
if (GetHeap()->GetContinuousSpaces()[0]->IsImageSpace()) {
class_linker_ = ClassLinker::CreateFromImage(intern_table_);
} else {
[...]
class_linker_ = ClassLinker::CreateFromCompiler(*options->boot_class_path_, intern_table_);
}
return true;
}
由程式代碼又再次見識到c++的抽象化,一個create函數又去呼叫一個init函數, 這代表在做這裡的設計希望把建立跟初始相關對象作隔離.然後再利用依賴關系将兩個方法做個連結.Create函數就隻是很單存的想實作設計模式裡的單一模式.而依些相關對象的初始化就由Runtime的init對象函數去完成.在init函數中可以看到先收集以及設定一些option,然後再new一塊heap對象來作為GC用, 雖然art在安裝時期就把所有的ByteCode轉譯為machine code,但也支援資源回收機制. 是以需要一個heap對象來做記憶體管理.接下來在new 一個JavaVMExt的對象,最後一個在dvm沒有見過的classLinker對象, art程式架構之是以會有這個新的classLinker class,是因為需要這個class來實作解析java layer的field和method的功能.在ClassLinker做動作之前有個啟動Thread并且作Attach動作,這個Attach fucntion 到底是做了哪些事情?
thread.cc
Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group,
bool create_peer) {
[...]
self = new Thread(as_daemon);
self->Init(runtime->GetThreadList(), runtime->GetJavaVM());
[...]
return self;
}
void Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm) {
[...]
SetUpAlternateSignalStack();
InitCpu();
InitTlsEntryPoints();
InitCardTable();
InitTid();
[...]
}
}
由上面的程式代碼不難發現Attach這函數做了一堆initial的動作.其中InitCpu和InitTlsEntryPoints跟硬體平台有關是以其實作會在\art\runtime\arch下,有興趣的人可以自行研究
4. 啟動執行環境
runtime.cc
bool Runtime::Start() {
[...]
InitNativeMethods();
[...]
if (is_zygote_) {
if (!InitZygote()) {
return false;
}
} else {
DidForkFromZygote();
}
[...]
return true;
}
由 start function 的程式代碼可以看到隻做兩件事, 初始化JNI和初始化Zygote或是做Zygote 複制的動作. 比起DVM在實作dvmStartup 函數來的相對簡單多了.
在一開始有說到目前預設的VM是dvm, 假若裝置若設為art時,哪開機有如何知道目前執行環境是dvm或是art? 這一切的答案盡在JniInvocationclass的init 函數中.
AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const char* options)
{
[...]
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
[...]
}
JniInvocation.cpp
#ifdef HAVE_ANDROID_OS
static const char* kLibrarySystemProperty = "persist.sys.dalvik.vm.lib";
#endif
static const char* kLibraryFallback = "libdvm.so";
bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
char default_library[PROPERTY_VALUE_MAX];
property_get(kLibrarySystemProperty, default_library, kLibraryFallback);
#else
const char* default_library = kLibraryFallback;
#endif
if (library == NULL) {
library = default_library;
}
handle_ = dlopen(library, RTLD_NOW);
[...]
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
"JNI_GetDefaultJavaVMInitArgs")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
"JNI_CreateJavaVM")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
"JNI_GetCreatedJavaVMs")) {
return false;
}
return true;
}
有此可以看到,在一開機時系統就會由這個屬性persist.sys.dalvik.vm.lib 來判斷目前要鍊結libdvm.so還是libart.so函式庫,這兩個函式庫的進入接口符号都是相同的,分别 JNI_GetDefaultJavaVMInitArgs, JNI_CreateJavaVM, JNI_GetCreatedJavaVMs.
5.結論
這一篇心得主要是在ART的初始化分析, 跟DVM的初始化相比,除了程式結構更抽象之外就是每一個動作都有特定的函數來實作顯得更加明确,當然有些程式架構看起來之後被重構的機會還頗大的.比如在Runtime::Init函數中有一行Thread* self = Thread::Attach("main", false, NULL, false);上面就有留以下的批注.
// ClassLinker needs an attached thread, but we can't fully attach a thread without creating // objects. We can't supply a thread group yet; it will be fixed later. Since we are the main // thread, we do not get a java peer. |
代表來日很有可能這個attach的動作會被重構.不管之後Google如何重構art程式代碼,我都非常期待Google将DVM和art整合在一起亦或完全用art把dvm取代掉的程式架構.