AOSP 8.0 系统启动之四ART虚拟机启动(一)
目录
前言
一、创建虚拟机
1.1 JniInvocation.Init
1.2 startVm
1.2.1 JNI_CreateJavaVM
1.3 startReg
前言
Dalvik虚拟机和ART虚拟机
- Dalvik虚拟机,基于apache的JVM 改进而来,为Android 第一代虚拟机。在Android 4.4之前使用。
- ART 虚拟机,也叫ART 模式,是第二代虚拟机,Android 4.4推出,并从5.0开始默认使用执行程序。
两者区别:
- Dalvik每次都要将apk代码编译成机器码再运行,Art只会首次启动编译,而不必每次运行都要先编译一次。
- Art占用空间比Dalvik大,首次安装Apk的时间比Dalvk模式长
- Art减少编译,减少了CPU使用频率,使用明显改善电池续航;
- 应用启动更快、运行更快、体验更流畅、触感反馈更及时
重点说下art虚拟机:
当然无论是 Dalvik 还是 Art,或者未来可能出现的新型虚拟机,它们提供的功能将全部封装在一个 so 库中,并且对外需要暴露 JNI_GetDefaultVMInitArgs、JNI_CreateVM 和 JNI_GetCreatedJavaVMs 三个标准接口,使用者(比如 Zygote)只需要按照统一的接口标准就可以控制和使用所有同类型的虚拟机了。
组成 Android 虚拟机的核心自系统包括但不限于 Runtime、ClassLoader System、Execution、Engine System、Heap Manager 和 GC 系统、JIT、JNI 环境等。
和标准的 JVM 一样,类加载器在 Android 虚拟机中也扮演者很重要的作用,可以分为 Boot ClassLoader、System ClassLoader、Dex ClassLoader 等,所有被加载的类和它们的组成元素都将由 ClassLinker 做统一的管理。
除了字节码解释执行的方式,Art 还支持通过 AOT 来直接执行字节码编译而成的机器码。
AOT 的编译时机有两个:
- 随 Android ROM 构建时一起编译。
- 程序安装时执行编译(针对第三方应用程序)。
Art 引入了新的存储格式,即 OAT 文件来存储编译后的机器代码。而 OAT 机器码的加载需要用到 ELF 的基础能力。
另外,由于一股脑地在程序安装阶段将 Dex 转化为 OAT 造成造成了一定的资源浪费,从 Android N 版本开始,Art 又改变了之前的 OAT 策略——程序在安装时不再统一执行 dex2oat,而改由根据程序的实际运行情况来决定有哪些部分需要被编译成本地代码,即恢复了 Interpreter、JIT、OAT 三足鼎立的局面。一方面,这种新变化大幅加快了程序的安装速度,解决了系统更新时用户需要经历漫长等待的问题;另一方面,由于程序的首次启动必须通过解释器来运行,Android N 版本必须采用多种手段(新的解释器,将 Verification 前移等)来保证程序的启动速度不受影响。
应用程序除了解释执行外,还会在运行过程中实时做 JIT 编译——不过它的结果并不会被持久化。另外,虚拟机会记录下应用程序在动态运行过程中被执行过的函数,并输出到 Profile 文件里。
AOT compile daemon 将在系统同时满足 idle 和充电状态两个条件时才会被唤醒,并按照一定的逻辑来遍历执行应用程序的 AOT 优化。由于参与 AOT 的函数数量通常只占应用程序代码的一小部分,所以整体而言 Android N 版本 AOT 结果所占用的空间大小比旧版本要小很多。
本文涉及源码
platform/frameworks/base/cmds/app_process/app_main.cpp
platform/frameworks/base/core/jni/AndroidRuntime.cpp
platform/libnativehelper/JniInvocation.cpp
一、创建虚拟机
创建虚拟机、注册JNI函数
platform/frameworks/base/core/jni/AndroidRuntime.cpp
// app_main.cpp
main(){
AppRuntime runtime;
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
}
// class AppRuntime : public AndroidRuntime // AppRuntime继承AndroidRuntime
// AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote){
//第一步:开始虚拟机
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
//第二步:注册系统jni
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
//第三步:进入zygoteinit.java main方法 startClass = com.android.internal.os.ZygoteInit
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([L;
}
//先重点分析第一步
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
... //打印一些日志,获取ANDROID_ROOT环境变量
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);//初始化JNI,加载libart.so
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote) != 0) {//创建虚拟机
return;
}
onVmCreated(env);//表示虚拟创建完成,但是里面是空实现
/*
* Register android functions.
*/
if (startReg(env) < 0) {注册JNI函数
ALOGE("Unable to register all android natives\n");
return;
}
... //JNI方式调用ZygoteInit类的main函数
}
1.1 JniInvocation.Init
定义在platform/libnativehelper/JniInvocation.cpp
bool JniInvocation::Init(const char* library) {
#ifdef __ANDROID__ //走这个分支
char buffer[PROP_VALUE_MAX];
#else
char* buffer = NULL;
#endif
library = GetLibrary(library, buffer);//默认返回 libart.so
const int kDlopenFlags = RTLD_NOW | RTLD_NODELETE;
/*
* 1.dlopen功能是以指定模式打开指定的动态链接库文件(elf文件),并返回一个句柄,dlopen的内容比较多,后续会单独讲elf的加载过程
* 2.RTLD_NOW表示需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL
* 3.RTLD_NODELETE表示在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量
*/
handle_ = dlopen(library, kDlopenFlags); // 获取libart.so的句柄
if (handle_ == NULL) { //获取失败打印错误日志并尝试再次打开libart.so
....异常重试处理
}
/*
* 1.FindSymbol函数内部实际调用的是dlsym
* 2.dlsym作用是根据 动态链接库 操作句柄(handle)与符号(symbol),返回符号对应的地址
* 3.这里实际就是从libart.so中将JNI_GetDefaultJavaVMInitArgs等对应的地址存入&JNI_GetDefaultJavaVMInitArgs_中
*/
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;
}
Init函数主要作用是初始化JNI,具体工作是首先通过dlopen加载libart.so获得其句柄,然后调用dlsym从libart.so中找到
JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM、JNI_GetCreatedJavaVMs三个函数地址,赋值给对应成员属性,
这三个函数会在后续虚拟机创建中调用.
1.2 startVm
定义在platform/frameworks/base/core/jni/AndroidRuntime.cpp
//pJavaVM & pEnv都是双重指针, 用于函数内部返回的;
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
JavaVMInitArgs initArgs;
/通过getprop的值设置JVM的堆栈信息
//主要介绍几个相关堆栈信息
//dalvik.vm.heapstartsize每次申请空间的大小,这个值越小,速度越慢,值越大越容易空间不够
//dalvik.vm.heapgrowthlimit 每个应用内存空间的大小
//dalvik.vm.heapsize large模式下每个应用空间内存大小 minifest配置android:largeHeap="true"
//dalvik.vm.heaptargetutilization 应用堆栈使用率一般都是0.75
...
//虚拟机结束的时候,会调用这个函数指针runtime_exit
addOption("exit", (void*) runtime_exit);各//将参数放入mOptions数组中
...
initArgs.version = JNI_VERSION_1_4;
initArgs.options = mOptions.editArray();//将mOptions赋值给initArgs
initArgs.nOptions = mOptions.size();
initArgs.ignoreUnrecognized = JNI_FALSE;
pJavaVM & pEnv都是双重指针, 用于函数内部返回的;
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {//调用libart.so的JNI_CreateJavaVM函数
ALOGE("JNI_CreateJavaVM failed\n");
return -1;
}
return 0;
}
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
}
jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
return JNI_CreateJavaVM_(p_vm, p_env, vm_args);//调用之前初始化的JNI_CreateJavaVM_
}
这个函数特别长,但是里面做的事情很单一,其实就是从各种系统属性中读取一些参数,然后通过addOption设置到AndroidRuntime的mOptions数组中存起来,
另外就是调用之前从libart.so中找到JNI_CreateJavaVM函数,并将这些参数传入
1.2.1 JNI_CreateJavaVM
//java_vm_ext.cc
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
RuntimeOptions options;
for (int i = 0; i < args->nOptions; ++i) {
JavaVMOption* option = &args->options[i];
options.push_back(std::make_pair(std::string(option->optionString), option- >extraInfo));
}
bool ignore_unrecognized = args->ignoreUnrecognized;
//创建Runtime对象
if (!Runtime::Create(options, ignore_unrecognized)) {
return JNI_ERR;
}
....
Runtime* runtime = Runtime::Current();
bool started = runtime->Start(); //Runtime启动
if (!started) { //如果启动失败,则释放当前的JniEnv & JavaVM 对象;
delete Thread::Current()->GetJniEnv();
delete runtime->GetJavaVM();
LOG(WARNING) << "CreateJavaVM failed";
return JNI_ERR;
}
...
//初始化两个jni相关最重要的结构JavaVM和JNIEnv
//函数不需要return, 直接是通过指针变量返回即可;
*p_env = Thread::Current()->GetJniEnv();
*p_vm = runtime->GetJavaVM();
}
1.3 startReg
定义在platform/frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
/*
* 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);
//设置Android创建线程的函数javaCreateThreadEtc,这个函数内部是通过Linux的clone来创建线程的
ALOGV("--- 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);//创建一个200容量的局部引用作用域,这个局部引用其实就是局部变量
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { //注册JNI函数
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);//释放局部引用作用域
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
startReg首先是设置了Android创建线程的处理函数,然后创建了一个200容量的局部引用作用域,用于确保不会出现OutOfMemoryException,
最后就是调用register_jni_procs进行JNI注册
3.1.4 register_jni_procs
定义在platform/frameworks/base/core/jni/AndroidRuntime.cpp
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) { //调用mProc
#ifndef NDEBUG
ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
return -1;
}
}
return 0;
}
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
它的处理是交给RegJNIRec的mProc,RegJNIRec是个很简单的结构体,mProc是个函数指针
我们看看register_jni_procs传入的RegJNIRec数组gRegJNI,里面就是一堆的函数指针
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_MemoryIntArray)
...
}
REG_JNI是一个宏定义
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
也就是说REG_JNI(register_com_android_internal_os_ZygoteInit)这句就相当于,{register_com_android_internal_os_ZygoteInit},
也就是将register_com_android_internal_os_ZygoteInit强转为 int (mProc)(JNIEnv) 这样一个方法指针,于是就可以array[i].mProc(env)这样调用,
等同于调用register_com_android_internal_os_ZygoteInit(JNIEnv* env)这个方法
再看看register_com_android_internal_os_ZygoteInit,这实际上是自定义JNI函数并进行动态注册的标准写法,
内部是调用JNI的RegisterNatives,这样注册后,Java类ZygoteInit的native方法nativeZygoteInit就会调用com_android_internal_os_ZygoteInit_nativeZygoteInit函数
int register_com_android_internal_os_ZygoteInit(JNIEnv* env)
{
const JNINativeMethod methods[] = {
{ "nativeZygoteInit", "()V",
(void*) com_android_internal_os_ZygoteInit_nativeZygoteInit },
};
return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit",
methods, NELEM(methods));
}
以上便是第一部分的内容,主要工作是从libart.so提取出JNI初始函数JNI_CreateJavaVM等,然后读取一些系统属性作为参数调用JNI_CreateJavaVM创建虚拟机,
在虚拟机创建完成后,动态注册一些native函数,接下来我们讲第二部分,反射调用ZygoteInit类的main函数