浅谈android源码之dalvik

本文详细解析了DVM的启动过程,包括Zygote进程的启动、虚拟机与内存管理、虚拟机与进程管理以及虚拟机解析dex过程。通过剖析DVM的内部结构和工作机制,帮助读者深入了解DVM的运作原理。

1. 初识DVM

Dalvik VM是Android程序的java虚拟机,代码位于源码的dalvik目录下,下面是目录结构:
在这里插入图片描述
Dalvik虚拟机重要目录说明如下所示:

◇ Android.mk: 是虚拟机编译的makefile文件
◇ vm:此目录包含虚拟机绝大多数代码,包括虚拟机初始化及内存管理的代码
◇ dx:此目录是生成将Java字节码转换为Dalvik机器码的工具
◇ hit:此目录是生成显示堆栈信息/对象信息的工具
◇ libdex: 此目录是生成主机和设备处理dex文件的库
◇ dexopt:此目录是生成dex优化工具
◇ dexdump:此目录是生成dex文件反编译查看工具
◇ dexlist:此目录是生成查看dex文件里所有类的方法的工具

下面主要是基于vm和libdex这两个目录的源码分析DVM原理,也可以直接看最后一节代码流程图对这篇文章有大体的认识,我也是根据这个流程来进行说明的。

2. 虚拟机的启动过程

2.1 前言

学习android DVM虚拟机之前先了解一下它的启动过程,这可以对虚拟机的实现原理有大概的思路。需要注意的是虚拟机的启动依赖zygote进程,zygote进程又依赖init进程的启动,所以接下来会从init进程启动开始学习虚拟机。

2.2 init启动Zygote

当Linux内核加载完后,要做的第一件事就是调用init程序,也就是说,init是用户空间执行的第一个程序。init进程即init.c程序。Android系统启动分为Standard mode与Recovery mode,他们分别加载的是system.img与recovery.img。所以在源码中会有两个init.c文件,这边只分析正常启动情况下init进程的启动过程。首先是跟踪到init的程序入口,即main函数:

====================== system/core/init/init.c ======================

init_parse_config_file("/init.rc");

由于init进程做了很多初始化相关操作,所以main函数的实现比较多,上面代码它的作用就是解析init.rc文件。

====================== system/core/init/init.c ======================

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

可以看到这里启动了一个名为zygote的进程,它启动依赖了一个位于
“/system/bin/app_process”程序,需要注意的是后面所带的参数,这个又与Systemserver进程的启动息息相关,这也算是题外话了。

2.3 Zygote启动DVM

============= frameworks/base/cmds/app_process/app_main.cpp ===========

int main(int argc, char* const argv[])
{
#ifdef __arm__
    /*
     * b/7188322 - Temporarily revert to the compat memory layout
     * to avoid breaking third party apps.
     *
     * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE.
     *
     * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466
     * changes the kernel mapping from bottom up to top-down.
     * This breaks some programs which improperly embed
     * an out of date copy of Android's linker.
     */
    char value[PROPERTY_VALUE_MAX];
    property_get("ro.kernel.qemu", value, "");
    bool is_qemu = (strcmp(value, "1") == 0);
    if ((getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) && !is_qemu) {
        int current = personality(0xFFFFFFFF);
        if ((current & ADDR_COMPAT_LAYOUT) == 0) {
            personality(current | ADDR_COMPAT_LAYOUT);
            setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1);
            execv("/system/bin/app_process", argv);
            return -1;
        }
    }
    unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP");
#endif

    // These are global variables in ProcessState.cpp
    mArgC = argc;
    mArgV = argv;

    mArgLen = 0;
    for (int i=0; i<argc; i++) {
        mArgLen += strlen(argv[i]) + 1;
    }
    mArgLen--;

    AppRuntime runtime;
    const char* argv0 = argv[0];

    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm

    int i = runtime.addVmArguments(argc, argv);

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    const char* parentDir = NULL;
    const char* niceName = NULL;
    const char* className = NULL;
    while (i < argc) {
        const char* arg = argv[i++];
        if (!parentDir) {
            parentDir = arg;
        } else if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = "zygote";
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName = arg + 12;
        } else {
            className = arg;
            break;
        }
    }

    if (niceName && *niceName) {
        setArgv0(argv0, niceName);
        set_process_name(niceName);
    }

    runtime.mParentDir = parentDir;

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        // Remainder of args get passed to startup class main()
        runtime.mClassName = className;
        runtime.mArgC = argc - i;
        runtime.mArgV = argv + i;
        runtime.start("com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

我们可以看第63行的代码,可以看出zygote这个变量的值为true,所以会执行86与87这两行代码,即调用了AndroidRuntime.cpp的start函数。

===============frameworks/base/core/jni/AndroidRuntime.cpp =============

void AndroidRuntime::start(const char* className, const char* options)
 {
     ALOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
             className != NULL ? className : "(unknown)");
 
     /*
      * 'startSystemServer == true' means runtime is obsolete and not run from
      * init.rc anymore, so we print out the boot start event here.
      */
     if (strcmp(options, "start-system-server") == 0) {
         /* track our progress through the boot sequence */
         const int LOG_BOOT_PROGRESS_START = 3000;
         LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
                        ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
     }
 
     const char* rootDir = getenv("ANDROID_ROOT");
     if (rootDir == NULL) {
         rootDir = "/system";
         if (!hasDir("/system")) {
             LOG_FATAL("No root directory specified, and /android does not exist.");
             return;
         }
         setenv("ANDROID_ROOT", rootDir, 1);
     }
 
     //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
     //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);
 
     /* start the virtual machine */
     JniInvocation jni_invocation;
     jni_invocation.Init(NULL);
     JNIEnv* env;
     if (startVm(&mJavaVM, &env) != 0) {
         return;
     }
     onVmCreated(env);
 
     /*
      * Register android functions.
      */
     if (startReg(env) < 0) {
         ALOGE("Unable to register all android natives\n");
         return;
     }
 
     /*
      * We want to call main() with a String array with arguments in it.
      * At present we have two arguments, the class name and an option string.
      * Create an array to hold them.
      */
     jclass stringClass;
     jobjectArray strArray;
     jstring classNameStr;
     jstring optionsStr;
 
     stringClass = env->FindClass("java/lang/String");
     assert(stringClass != NULL);
     strArray = env->NewObjectArray(2, stringClass, NULL);
     assert(strArray != NULL);
     classNameStr = env->NewStringUTF(className);
     assert(classNameStr != NULL);
     env->SetObjectArrayElement(strArray, 0, classNameStr);
     optionsStr = env->NewStringUTF(options);
     env->SetObjectArrayElement(strArray, 1, optionsStr);
 
     /*
      * Start VM.  This thread becomes the main thread of the VM, and will
      * not return until the VM exits.
      */
     char* slashClassName = toSlashClassName(className);
     jclass startClass = env->FindClass(slashClassName);
     if (startClass == NULL) {
         ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
         /* keep going */
     } else {
         jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
             "([Ljava/lang/String;)V");
         if (startMeth == NULL) {
             ALOGE("JavaVM unable to find main() in '%s'\n", className);
             /* keep going */
         } else {
             env->CallStaticVoidMethod(startClass, startMeth, strArray);
 
 #if 0
             if (env->ExceptionCheck())
                 threadExitUncaughtException(env);
 #endif
         }
     }
     free(slashClassName);
 
     ALOGD("Shutting down VM\n");
     if (mJavaVM->DetachCurrentThread() != JNI_OK)
         ALOGW("Warning: unable to detach main thread\n");
     if (mJavaVM->DestroyJavaVM() != 0)
         ALOGW("Warning: VM did not shut down cleanly\n");
 }

上面的代码中对于虚拟机的启动来说需要注意第34行的startVm函数与42行startReg函数。

2.3.1 startVm

===============frameworks/base/core/jni/AndroidRuntime.cpp =============
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv){
……
}
startVm函数的实现非常多,这里就不贴出来了,只需要注意里面第776行代码中调用到的
JNI_CreateJavaVM函数,它的实现代码又在其他的cpp文件中,我们继续往下跟踪他的实现代码:

========================dalvik /vm/Jni.cpp =======================

jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
    const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
    if (dvmIsBadJniVersion(args->version)) {
        ALOGE("Bad JNI version passed to CreateJavaVM: %d", args->version);
        return JNI_EVERSION;
    }

    // TODO: don't allow creation of multiple VMs -- one per customer for now

    /* zero globals; not strictly necessary the first time a VM is started */
    memset(&gDvm, 0, sizeof(gDvm));

    /*
     * Set up structures for JNIEnv and VM.
     */
    JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
    pVM->funcTable = &gInvokeInterface;
    pVM->envList = NULL;
    dvmInitMutex(&pVM->envListLock);

    UniquePtr<const char*[]> argv(new const char*[args->nOptions]);
    memset(argv.get(), 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.
     */
    int argc = 0;
    for (int i = 0; i < args->nOptions; i++) {
        const char* optStr = args->options[i].optionString;
        if (optStr == NULL) {
            dvmFprintf(stderr, "ERROR: CreateJavaVM failed: argument %d was NULL\n", i);
            return JNI_ERR;
        } else if (strcmp(optStr, "vfprintf") == 0) {
            gDvm.vfprintfHook = (int (*)(FILE *, const char*, va_list))args->options[i].extraInfo;
        } else if (strcmp(optStr, "exit") == 0) {
            gDvm.exitHook = (void (*)(int)) args->options[i].extraInfo;
        } else if (strcmp(optStr, "abort") == 0) {
            gDvm.abortHook = (void (*)(void))args->options[i].extraInfo;
        } else if (strcmp(optStr, "sensitiveThread") == 0) {
            gDvm.isSensitiveThreadHook = (bool (*)(void))args->options[i].extraInfo;
        } else if (strcmp(optStr, "-Xcheck:jni") == 0) {
            gDvmJni.useCheckJni = true;
        } else if (strncmp(optStr, "-Xjniopts:", 10) == 0) {
            char* jniOpts = strdup(optStr + 10);
            size_t jniOptCount = 1;
            for (char* p = jniOpts; *p != 0; ++p) {
                if (*p == ',') {
                    ++jniOptCount;
                    *p = 0;
                }
            }
            char* jniOpt = jniOpts;
            for (size_t i = 0; i < jniOptCount; ++i) {
                if (strcmp(jniOpt, "warnonly") == 0) {
                    gDvmJni.warnOnly = true;
                } else if (strcmp(jniOpt, "forcecopy") == 0) {
                    gDvmJni.forceCopy = true;
                } else if (strcmp(jniOpt, "logThirdPartyJni") == 0) {
                    gDvmJni.logThirdPartyJni = true;
                } else {
                    dvmFprintf(stderr, "ERROR: CreateJavaVM failed: unknown -Xjniopts option '%s'\n",
                            jniOpt);
                    free(pVM);
                    free(jniOpts);
                    return JNI_ERR;
                }
                jniOpt += strlen(jniOpt) + 1;
            }
            free(jniOpts);
        } else {
            /* regular option */
            argv[argc++] = optStr;
        }
    }

    if (gDvmJni.useCheckJni) {
        dvmUseCheckedJniVm(pVM);
    }

    if (gDvmJni.jniVm != NULL) {
        dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\n");
        free(pVM);
        return JNI_ERR;
    }
    gDvmJni.jniVm = (JavaVM*) pVM;

    /*
     * Create a JNIEnv for the 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.
     */
    JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);

    /* Initialize VM. */
    gDvm.initializing = true;
    std::string status =
            dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
    gDvm.initializing = false;

    if (!status.empty()) {
        free(pEnv);
        free(pVM);
        ALOGW("CreateJavaVM failed: %s", status.c_str());
        return JNI_ERR;
    }

    /*
     * Success!  Return stuff to caller.
     */
    dvmChangeStatus(NULL, THREAD_NATIVE);
    *p_env = (JNIEnv*) pEnv;
    *p_vm = (JavaVM*) pVM;
    ALOGV("CreateJavaVM succeeded");
    return JNI_OK;
}

JNI_CreateJavaVM函数中需要注意的是96行dvmCreateJNIEnv函数与101行dvmStartup函数。首先来看这个dvmCreateJNIEnv函数他做了哪些工作。从它的名字可以猜到他建立了jni的环境,没错,确实是这样,哈哈哈。

 JNIEnv* dvmCreateJNIEnv(Thread* self) {
     JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm;
 
     //if (self != NULL)
     //    ALOGI("Ent CreateJNIEnv: threadid=%d %p", self->threadId, self);
 
     assert(vm != NULL);
 
     JNIEnvExt* newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
     newEnv->funcTable = &gNativeInterface;
     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;
     }
     if (gDvmJni.useCheckJni) {
         dvmUseCheckedJniEnv(newEnv);
     }
 
     ScopedPthreadMutexLock lock(&vm->envListLock);
 
     /* 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;
 
     //if (self != NULL)
     //    ALOGI("Xit CreateJNIEnv: threadid=%d %p", self->threadId, self);
     return (JNIEnv*) newEnv;
 }


上面代码中需要注意的是第10行代码取了gNativeInterface结构体指针并赋值给了
jni的环境指针变量newEnv的funcTable属性。
其实只要看gNativeInterface结构体就大概知道是怎么一回事了。

static const struct JNINativeInterface gNativeInterface = {
    NULL,
    NULL,
    NULL,
    NULL,

    GetVersion,

    DefineClass,
    FindClass,
    .......
};

其实就是后面使用jni可以通过这个环境变量调用这些函数。

再回头看dvmStartup这个函数,它的实现的东西比较多,需要慢慢来看。

========================dalvik /vm/Init.cpp =======================

std::string dvmStartup(int argc, const char* const argv[],
        bool ignoreUnrecognized, JNIEnv* pEnv)
{
    ScopedShutdown scopedShutdown;

    assert(gDvm.initializing);

    ALOGV("VM init args (%d):", argc);
    for (int i = 0; i < argc; i++) {
        ALOGV("  %d: '%s'", i, argv[i]);
    }
    setCommandLineDefaults();

    /*
     * Process the option flags (if any).
     */
    int cc = processOptions(argc, argv, ignoreUnrecognized);
    if (cc != 0) {
        if (cc < 0) {
            dvmFprintf(stderr, "\n");
            usage("dalvikvm");
        }
        return "syntax error";
    }

#if WITH_EXTRA_GC_CHECKS > 1
    /* only "portable" interp has the extra goodies */
    if (gDvm.executionMode != kExecutionModeInterpPortable) {
        ALOGI("Switching to 'portable' interpreter for GC checks");
        gDvm.executionMode = kExecutionModeInterpPortable;
    }
#endif

    /* Configure group scheduling capabilities */
    if (!access("/dev/cpuctl/tasks", F_OK)) {
        ALOGV("Using kernel group scheduling");
        gDvm.kernelGroupScheduling = 1;
    } else {
        ALOGV("Using kernel scheduler policies");
    }

    /* configure signal handling */
    if (!gDvm.reduceSignals)
        blockSignals();

    /* verify system page size */
    if (sysconf(_SC_PAGESIZE) != SYSTEM_PAGE_SIZE) {
        return StringPrintf("expected page size %d, got %d",
                SYSTEM_PAGE_SIZE, (int) sysconf(_SC_PAGESIZE));
    }

    /* mterp setup */
    ALOGV("Using executionMode %d", gDvm.executionMode);
    dvmCheckAsmConstants();

    /*
     * Initialize components.
     */
    dvmQuasiAtomicsStartup();
    if (!dvmAllocTrackerStartup()) {
        return "dvmAllocTrackerStartup failed";
    }
    if (!dvmGcStartup()) {
        return "dvmGcStartup failed";
    }
    if (!dvmThreadStartup()) {
        return "dvmThreadStartup failed";
    }
    if (!dvmInlineNativeStartup()) {
        return "dvmInlineNativeStartup";
    }
    if (!dvmRegisterMapStartup()) {
        return "dvmRegisterMapStartup failed";
    }
    if (!dvmInstanceofStartup()) {
        return "dvmInstanceofStartup failed";
    }
    if (!dvmClassStartup()) {
        return "dvmClassStartup failed";
    }

    /*
     * At this point, the system is guaranteed to be sufficiently
     * initialized that we can look up classes and class members. This
     * call populates the gDvm instance with all the class and member
     * references that the VM wants to use directly.
     */
    if (!dvmFindRequiredClassesAndMembers()) {
        return "dvmFindRequiredClassesAndMembers failed";
    }

    if (!dvmStringInternStartup()) {
        return "dvmStringInternStartup failed";
    }
    if (!dvmNativeStartup()) {
        return "dvmNativeStartup failed";
    }
    if (!dvmInternalNativeStartup()) {
        return "dvmInternalNativeStartup failed";
    }
    if (!dvmJniStartup()) {
        return "dvmJniStartup failed";
    }
    if (!dvmProfilingStartup()) {
        return "dvmProfilingStartup failed";
    }

    /*
     * Create a table of methods for which we will substitute an "inline"
     * version for performance.
     */
    if (!dvmCreateInlineSubsTable()) {
        return "dvmCreateInlineSubsTable failed";
    }

    /*
     * Miscellaneous class library validation.
     */
    if (!dvmValidateBoxClasses()) {
        return "dvmValidateBoxClasses failed";
    }

    /*
     * Do the last bits of Thread struct initialization we need to allow
     * JNI calls to work.
     */
    if (!dvmPrepMainForJni(pEnv)) {
        return "dvmPrepMainForJni failed";
    }

    /*
     * Explicitly initialize java.lang.Class.  This doesn't happen
     * automatically because it's allocated specially (it's an instance
     * of itself).  Must happen before registration of system natives,
     * which make some calls that throw assertions if the classes they
     * operate on aren't initialized.
     */
    if (!dvmInitClass(gDvm.classJavaLangClass)) {
        return "couldn't initialized java.lang.Class";
    }

    /*
     * Register the system native methods, which are registered through JNI.
     */
    if (!registerSystemNatives(pEnv)) {
        return "couldn't register system natives";
    }

    /*
     * Do some "late" initialization for the memory allocator.  This may
     * allocate storage and initialize classes.
     */
    if (!dvmCreateStockExceptions()) {
        return "dvmCreateStockExceptions failed";
    }

    /*
     * 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()) {
        return "dvmPrepMainThread failed";
    }

    /*
     * Make sure we haven't accumulated any tracked references.  The main
     * thread should be starting with a clean slate.
     */
    if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
    {
        ALOGW("Warning: tracked references remain post-initialization");
        dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
    }

    /* general debugging setup */
    if (!dvmDebuggerStartup()) {
        return "dvmDebuggerStartup failed";
    }

    if (!dvmGcStartupClasses()) {
        return "dvmGcStartupClasses failed";
    }

    /*
     * 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 (!initZygote()) {
            return "initZygote failed";
        }
    } else {
        if (!dvmInitAfterZygote()) {
            return "dvmInitAfterZygote failed";
        }
    }


#ifndef NDEBUG
    if (!dvmTestHash())
        ALOGE("dvmTestHash FAILED");
    if (false /*noisy!*/ && !dvmTestIndirectRefTable())
        ALOGE("dvmTestIndirectRefTable FAILED");
#endif

    if (dvmCheckException(dvmThreadSelf())) {
        dvmLogExceptionStackTrace();
        return "Exception pending at end of VM initialization";
    }

    scopedShutdown.disarm();
    return "";
}

需要注意的函数说明:

  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等。

  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类等。

  17. 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。

  18. dvmPrepMainForJni
    这个函数定义在文件dalvik/vm/Thread.c中,用来准备主线程的JNI环境,即将在前面的Step 5中为主线程创建的Thread对象与在前面Step 4中创建的JNI环境关联起来。回忆在前面的Step 4中,虽然我们已经为当前线程创建好一个JNI环境了,但是还没有将该JNI环境与主线程关联,也就是还没有将主线程的ID设置到该JNI环境中去。

  19. registerSystemNatives
    这个函数定义在文件dalvik/vm/Init.c中,它调用另外一个函数jniRegisterSystemMethods,后者接着又调用了函数registerCoreLibrariesJni来为Java核心类注册JNI方法。函数registerCoreLibrariesJni定义在文件libcore/luni/src/main/native/Register.cpp中。

  20. dvmCreateStockExceptions
    这个函数定义在文件dalvik/vm/alloc/Alloc.c中,用来预创建一些与内存分配有关的异常对象,并且将它们缓存起来,以便以后可以快速使用。这些异常对象包括java.lang.OutOfMemoryError、java.lang.InternalError和java.lang.NoClassDefFoundError。

  21. dvmPrepMainThread
    这个函数定义在文件dalvik/vm/Thread.c中,用来为主线程创建一个java.lang.ThreadGroup对象、一个java.lang.Thread对角和java.lang.VMThread对象。这些Java对象和在前面Step 5中创建的C++层Thread对象关联一起,共同用来描述Dalvik虚拟机的主线程。

  22. dvmReferenceTableEntries
    这个函数定义在文件dalvik/vm/ReferenceTable.h中,用来确保主线程当前不引用有任何Java对象,这是为了保证主线程接下来以干净的方式来执行程序入口。

  23. dvmDebuggerStartup
    这个函数定义在文件dalvik/vm/Debugger.c中,用来初始化Dalvik虚拟机的调试环境。注意,Dalvik虚拟机与Java虚拟机一样,都是通过JDWP协议来支持远程调试的。

2.3.2 startReg

startReg函数的作用其实就是注册一些jni函数,跟随Zygote启动而注册,不同于动态注册中的重写JNI_OnLoad实现函数的注册。接下来来看这里是如何实现函数的注册的。

===============frameworks/base/core/jni/AndroidRuntime.cpp =============

/*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);

    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);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

上面代码中需要注意的是第20行的代码中register_jni_procs函数的这个gRegJNI参数。而gRegJNI这个变量实际是一个集合:

static const RegJNIRec gRegJNI[] = {
……
REG_JNI(register_android_debug_JNITest),
REG_JNI(register_com_android_internal_os_RuntimeInit),
……
}

通过REG_JNI这个函数,将函数名以参数传入实现函数的注册。

3. 虚拟机与内存管理

3.1 前言

DVM的内存管理对于虚拟机的正常运行起到至关重要的作用,它包括了类对象,基本数据类型,以及线程的管理,原生的采用的是标记-清除(Mark-Sweep)算法进行内存管理的,即先标记被引用的对象,然后再清理未被标记的对象。

3.2 启动GC的时机

Dalvik虚拟机在三种情况下会触发四种类型的GC。每一种类型GC使用一个GcSpec结构体来描述,它的定义如下所示:
====================dalvik/vm/alloc/Heap.h ====================

struct GcSpec {
  /* If true, only the application heap is threatened. */
  bool isPartial;
  /* If true, the trace is run concurrently with the mutator. */
  bool isConcurrent;
  /* Toggles for the soft reference clearing policy. */
  bool doPreserve;
  /* A name for this garbage collection mode. */
  const char *reason;
};

GcSpec结构体的各个成员变量的含义如下所示:

isPartial: 为true时,表示仅仅回收Active堆的垃圾;为false时,表示同时回收Active堆和Zygote堆的垃圾。

isConcurrent: 为true时,表示执行并行GC;为false时,表示执行非并行GC。

doPreserve: 为true时,表示在执行GC的过程中,不回收软引用引用的对象;为false时,表示在执行GC的过程中,回收软引用引用的对象。

reason: 一个描述性的字符串。

Davlik虚拟机定义了四种类的GC,如下所示:

/* Not enough space for an "ordinary" Object to be allocated. */
extern const GcSpec *GC_FOR_MALLOC;

/* Automatic GC triggered by exceeding a heap occupancy threshold. */
extern const GcSpec *GC_CONCURRENT;

/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
extern const GcSpec *GC_EXPLICIT;

/* Final attempt to reclaim memory before throwing an OOM. */
extern const GcSpec *GC_BEFORE_OOM;

它们的含义如下所示:
GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。
GC_CONCURRENT: 表示是在已分配内存达到一定量之后触发的GC。
GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

实际上,GC_FOR_MALLOC、GC_CONCURRENT和GC_BEFORE_OOM三种类型的GC都是在分配对象的过程触发的。

Dalvik虚拟机在Java堆上分配对象的时候,在碰到分配失败的情况,会尝试调用函数gcForMalloc进行垃圾回收。

总的来说,在 Dalvik 虚拟机中,下述情况下 GC 会启动。

  1. VM Heap 没有空闲空间,对象内存分配失败时,调用gcForMalloc进行垃圾回收,参数为GC_BEFORE_OOM或GC_FOR_MALLOC,最终调用dvmCollectGarbageInternal。
  2. VM Heap在已分配内存达到一定量之后触发的GC,即空闲内存不足时会唤醒GC线程,最终调用dvmCollectGarbageInternal(GC_CONCURRENT)函数进行垃圾回收
  3. 当应用程序调用System.gc、VMRuntime.gc接口,或者接收到SIGUSR1信号时,会调到dvmCollectGarbage,最终会调用dvmCollectGarbageInternal(GC_EXPLICIT)进行垃圾回收 。

3.3 标记阶段

首先来简单说明一下标记的顺序。虽然说是顺序,也只是分成两个步骤而已。

  1. 标记从根引用的对象
  2. 搜索已标记的对象

====================dalvik/vm/alloc/Heap.c ======================

Void dvmCollectGarbageInternal(bool collectSoftReferences)
{ 
...... 
dvmSuspendAllThreads(SUSPEND_FOR_GC); /* 停止全部线程 */ 
dvmHeapMarkRootSet();/* 调用与标记相关的函数群:标记阶段 */
dvmHeapSweepUnmarkedObjects();/* 调用与清除相关的函数群:清除阶段 */
dvmResumeAllThreads(SUSPEND_FOR_GC); /* 启动全部停止的线程 */
......
}

=====================dalvik/vm/alloc/MarkSweep.cpp ================


/* Mark the set of root objects.
 *
 * Things we need to scan:
 * - System classes defined by root classloader
 * - For each thread:
 *   - Interpreted stack, from top to "curFrame"
 *     - Dalvik registers (args + local vars)
 *   - JNI local references
 *   - Automatic VM local references (TrackedAlloc)
 *   - Associated Thread/VMThread object
 *   - ThreadGroups (could track & start with these instead of working
 *     upward from Threads)
 *   - Exception currently being thrown, if present
 * - JNI global references
 * - Interned string table
 * - Primitive classes
 * - Special objects
 *   - gDvm.outOfMemoryObj
 * - Objects in debugger object registry
 *
 * Don't need:
 * - Native stack (for in-progress stuff in the VM)
 *   - The TrackedAlloc stuff watches all native VM references.
 */
void dvmHeapMarkRootSet()
{
    GcHeap *gcHeap = gDvm.gcHeap;
    dvmMarkImmuneObjects(gcHeap->markContext.immuneLimit);
    dvmVisitRoots(rootMarkObjectVisitor, &gcHeap->markContext);
}

通过注释我们可以看到根集对象都包含了哪些对象。总的来说,就是包含两大类对象。一类是Dalvik虚拟机内部使用的全局对象,另一类是应用程序正在使用的对象。前者会维护在内部的一些数据结构中,例如Hash表,后者维护在调用栈中。对这些根集对象进行标记都是通过函数dvmVisitRoots和回调函数rootMarkObjectVisitor进行的。
此外,我们还需要将不在堆回收范围内的对象也看作是根集对象,以便后面可以使用统一的方法来遍历这两类对象所引用的其它对象。标记不在堆回收范围内的对象是通过函数dvmMarkImmuneObjects来实现的。

3.4 清除阶段

标记阶段之后是清除阶段。清除阶段中要释放没有打上标记的非活动对象。
首先,对象位图内记录了分配到 VM Heap 内的对象。
另外,标记位图内记录了其中的活动对象。 清除操作首先必须找到释放对象,也就是非活动对象。由以上信息可知,如果把对象位图 和标记位图适当地取差分,应该很容易就能找到非活动对象了。

=====================dalvik/vm/alloc/Heap.c =====================

Void dvmCollectGarbageInternal(bool collectSoftReferences)
{ 
...... 
dvmSuspendAllThreads(SUSPEND_FOR_GC); /* 停止全部线程 */ 
dvmHeapMarkRootSet();/* 调用与标记相关的函数群:标记阶段 */
dvmHeapSweepUnmarkedObjects();/* 调用与清除相关的函数群:清除阶段 */
dvmResumeAllThreads(SUSPEND_FOR_GC); /* 启动全部停止的线程 */
...... 
}

===================dalvik/vm/alloc/ MarkSweep.cpp ===================

/*
 * Walk through the list of objects that haven't been marked and free
 * them.  Assumes the bitmaps have been swapped.
 */
void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent,
                                 size_t *numObjects, size_t *numBytes)
{
    uintptr_t base[HEAP_SOURCE_MAX_HEAP_COUNT];
    uintptr_t max[HEAP_SOURCE_MAX_HEAP_COUNT];
    SweepContext ctx;
    HeapBitmap *prevLive, *prevMark;
    size_t numHeaps, numSweepHeaps;

    numHeaps = dvmHeapSourceGetNumHeaps();
    dvmHeapSourceGetRegions(base, max, numHeaps);
    if (isPartial) {
        assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == base[0]);
        numSweepHeaps = 1;
    } else {
        numSweepHeaps = numHeaps;
    }
    ctx.numObjects = ctx.numBytes = 0;
    ctx.isConcurrent = isConcurrent;
    prevLive = dvmHeapSourceGetMarkBits();
    prevMark = dvmHeapSourceGetLiveBits();
    for (size_t i = 0; i < numSweepHeaps; ++i) {
        dvmHeapBitmapSweepWalk(prevLive, prevMark, base[i], max[i],
                               sweepBitmapCallback, &ctx);
    }
    *numObjects = ctx.numObjects;
    *numBytes = ctx.numBytes;
    if (gDvm.allocProf.enabled) {
        gDvm.allocProf.freeCount += ctx.numObjects;
        gDvm.allocProf.freeSize += ctx.numBytes;
    }
}

前面提到,当当前GC执行的是部分垃圾回收时,即参数isPartial等于true时,只有Active堆的垃圾会被回收。由于Active堆总是在base[0],所以函数dvmHeapSweepUnmarkedObjects在只执行部分垃圾回收时,将变量numSweepHeaps的值设置为1,就可以保证在后面的for循环只会遍历和清除Active堆的垃圾。

此外,变量prevLive和preMark指向的分别是当前Mark Bitmap和Live Bitmap,但是由于在执行函数dvmHeapSweepUnmarkedObjects之前,Mark Bitmap和Live Bitmap已经被交换,因此,变量prevLive和preMark实际上指向的Bitmap描述的分别上次GC后仍然存活的对象和当前GC后仍然存活的对象。
准备工作完成之后,函数dvmHeapSweepUnmarkedObjects调用函数dvmHeapBitmapSweepWalk来遍历上次GC后仍存活但是当前GC后不再存活的对象。这些对象正是要被回收的对象,它们通过函数sweepBitmapCallback来回收。

函数dvmHeapBitmapSweepWalk的实现如下所示:

===================dalvik/vm/alloc/HeapBitmap.cpp ===================

/*
 * Walk through the bitmaps in increasing address order, and find the
 * object pointers that correspond to garbage objects.  Call
 * <callback> zero or more times with lists of these object pointers.
 *
 * The callback is not permitted to increase the max of either bitmap.
 */
void dvmHeapBitmapSweepWalk(const HeapBitmap *liveHb, const HeapBitmap *markHb,
                            uintptr_t base, uintptr_t max,
                            BitmapSweepCallback *callback, void *callbackArg)
{
    assert(liveHb != NULL);
    assert(liveHb->bits != NULL);
    assert(markHb != NULL);
    assert(markHb->bits != NULL);
    assert(liveHb->base == markHb->base);
    assert(liveHb->bitsLen == markHb->bitsLen);
    assert(callback != NULL);
    assert(base <= max);
    assert(base >= liveHb->base);
    assert(max <= liveHb->max);
    if (liveHb->max < liveHb->base) {
        /* Easy case; both are obviously empty.
         */
        return;
    }
    void *pointerBuf[4 * HB_BITS_PER_WORD];
    void **pb = pointerBuf;
    size_t start = HB_OFFSET_TO_INDEX(base - liveHb->base);
    size_t end = HB_OFFSET_TO_INDEX(max - liveHb->base);
    unsigned long *live = liveHb->bits;
    unsigned long *mark = markHb->bits;
    for (size_t i = start; i <= end; i++) {
        unsigned long garbage = live[i] & ~mark[i];
        if (UNLIKELY(garbage != 0)) {
            unsigned long highBit = 1 << (HB_BITS_PER_WORD - 1);
            uintptr_t ptrBase = HB_INDEX_TO_OFFSET(i) + liveHb->base;
            while (garbage != 0) {
                int shift = CLZ(garbage);
                garbage &= ~(highBit >> shift);
                *pb++ = (void *)(ptrBase + shift * HB_OBJECT_ALIGNMENT);
            }
            /* Make sure that there are always enough slots available */
            /* for an entire word of 1s. */
            if (pb >= &pointerBuf[NELEM(pointerBuf) - HB_BITS_PER_WORD]) {
                (*callback)(pb - pointerBuf, pointerBuf, callbackArg);
                pb = pointerBuf;
            }
        }
    }
    if (pb > pointerBuf) {
        (*callback)(pb - pointerBuf, pointerBuf, callbackArg);
    }
}

函数dvmHeapSweepUnmarkedObjects的实现逻辑很简单,它在在参数liveGB描述的Bitmap中位等于1但是在参数markHb描述的Bitmap中位却等于0的对象,也就是在上次GC后仍然存活的但是在当前GC后却不存活的对象,并且将这些对象的地址收集在变量pointerBuf描述的数组中,最后调用参数callback指向的回调函数批量回收保存在该数组中的对象。
参数callback指向的回调函数为sweepBitmapCallback

===================dalvik/vm/alloc/MarkSweep.cpp ===================

static void sweepBitmapCallback(size_t numPtrs, void **ptrs, void *arg)
{
    assert(arg != NULL);
    SweepContext *ctx = (SweepContext *)arg;
    if (ctx->isConcurrent) {
        dvmLockHeap();
    }
    ctx->numBytes += dvmHeapSourceFreeList(numPtrs, ptrs);
    ctx->numObjects += numPtrs;
    if (ctx->isConcurrent) {
        dvmUnlockHeap();
    }
}

函数dvmHeapSourceFreeList是真正进行内存回收的地方。

====================dalvik/vm/alloc/HeapSource.cpp ==================

/*
 * Frees the first numPtrs objects in the ptrs list and returns the
 * amount of reclaimed storage. The list must contain addresses all in
 * the same mspace, and must be in increasing order. This implies that
 * there are no duplicates, and no entries are NULL.
 */
size_t dvmHeapSourceFreeList(size_t numPtrs, void **ptrs)
{
    HS_BOILERPLATE();

    if (numPtrs == 0) {
        return 0;
    }

    assert(ptrs != NULL);
    assert(*ptrs != NULL);
    Heap* heap = ptr2heap(gHs, *ptrs);
    size_t numBytes = 0;
    if (heap != NULL) {
        mspace msp = heap->msp;
        // Calling mspace_free on shared heaps disrupts sharing too
        // much. For heap[0] -- the 'active heap' -- we call
        // mspace_free, but on the other heaps we only do some
        // accounting.
        if (heap == gHs->heaps) {
            // Count freed objects.
            for (size_t i = 0; i < numPtrs; i++) {
                assert(ptrs[i] != NULL);
                assert(ptr2heap(gHs, ptrs[i]) == heap);
                countFree(heap, ptrs[i], &numBytes);
            }
            // Bulk free ptrs.
            mspace_bulk_free(msp, ptrs, numPtrs);
        } else {
            // This is not an 'active heap'. Only do the accounting.
            for (size_t i = 0; i < numPtrs; i++) {
                assert(ptrs[i] != NULL);
                assert(ptr2heap(gHs, ptrs[i]) == heap);
                countFree(heap, ptrs[i], &numBytes);
            }
        }
    }
    return numBytes;
}

函数dvmHeapSourceFreeList首先调用函数计算要释放的内存所属的堆heap,即Active堆还是Zygote堆。Active堆保存在gHs->heaps指向的Heap数组的第一个元素,根据这个信息就可以知道堆heap是Active堆还是Zygote堆。

对于Active堆,函数dvmHeapSourceFreeList首先是对每一块要释放的内存调用函数countFree进行计账,然后再调用C库提供的接口mspace_build_free批量释放参数ptrs指向的一系列内存块。

对于Zygote堆,函数dvmHeapSourceFreeList仅仅是对每一块要释放的内存调用函数countFree进行计账,但是并没有真正释放对应的内存块。这是因为Zygote堆是Zygote进程和所有的应用程序进程共享。一旦某一个进程对它进行了写类型的操作,那么就会导致对应的内存块不再在Zygote进程和应用程序进程之间进行共享。这样对Zygote堆的内存块进行释放反而会增加物理内存的开销。

4. 虚拟机与进程管理

4.1 前言

dalvik进程管理是依赖于linux的进程体系结构的,如要为应用程序创建一个进程,它会使用linux的fork机制来复制一个进程(复制进程往往比创建进程效率更高)。

Zygote是一个虚拟机进程,同时也是一个虚拟机实例的孵化器,它通过init进程启动。首先会孵化出System_Server(android绝大多系统服务的守护进程,它会监听socket等待请求命令,当有一个应用程序启动时,就会向它发出请求,zygote就会FORK出一个新的应用程序进程).每当系统要求执行一个android应用程序时,Zygote就会运用linux的FORK进制产生一个子进程来执行该应用程序。下面简单说明一下进程管理实现原理。

4.2 客户端请求

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
    try {
       ......
      // Start the process.  It will either succeed and return a result containing
      // the PID of the new process, or else throw a RuntimeException.
     Process.ProcessStartResult startResult= Process.start("android.app.ActivityThread",
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, null);
      .......
    }
}

===========/frameworks/base/core/java/android/os/Process.java =========

public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String[] zygoteArgs) {
try {
   return startViaZygote(processClass, niceName, uid, gid, gids,
          debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
 } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
   }
}

===========/frameworks/base/core/java/android/os/Process.java =========

private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            ……
            return zygoteSendArgsAndGetResult(argsForZygote);
        }
}

startViaZygote的绝大部分代码都在处理传递到Zygote中的参数,与Zygote通信通过zygoteSendArgsAndGetResult()方法完成:
===========/frameworks/base/core/java/android/os/Process.java =========

private static ProcessStartResult zygoteSendArgsAndGetResult(ArrayList<String> args)
            throws ZygoteStartFailedEx {
        openZygoteSocketIfNeeded();//确保和Zygote通信的socket已被打开
        try {
            sZygoteWriter.write(Integer.toString(args.size()));
            sZygoteWriter.newLine();
            int sz = args.size();
            for (int i = 0; i < sz; i++) {//发送请求参数到Zygote
                String arg = args.get(i);
                if (arg.indexOf('\n') >= 0) {
                    throw new ZygoteStartFailedEx(
                            "embedded newlines not allowed");
                }
                sZygoteWriter.write(arg);
                sZygoteWriter.newLine();
            }
            sZygoteWriter.flush();
            ProcessStartResult result = new ProcessStartResult();
            result.pid = sZygoteInputStream.readInt();  
          if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }
            result.usingWrapper = sZygoteInputStream.readBoolean();
            return result;
        } catch (IOException ex) {
            try {
                if (sZygoteSocket != null) {
                    sZygoteSocket.close();
                }
            } catch (IOException ex2) {
                // we're going to fail anyway
                Log.e(LOG_TAG,"I/O exception on routine close", ex2);
            }
            sZygoteSocket = null;
            throw new ZygoteStartFailedEx(ex);
        }
    }


4.3 处理客户端请求

首先是到ZygoteInit.java main函数这里开始观察
===/frameworks/base/core/java/com/android/internal/os/ZygoyeInit.java ====

public static void main(String argv[]) {
……
runSelectLoop();
……
}

核心代码在runSelectLoop函数的实现中,继续往下看

===/frameworks/base/core/java/com/android/internal/os/ZygoyeInit.java ====

private static void runSelectLoop() throws MethodAndArgsCaller {
……
	white(true){
……
		    if (index < 0) {
                throw new RuntimeException("Error in select()");
            } else if (index == 0) {
                ZygoteConnection newPeer = acceptCommandPeer();
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done;
                done = peers.get(index).runOnce();
                if (done) {
                    peers.remove(index);
                    fds.remove(index);
                }
            }
}
}

上面函数实现中,最终会调用到runOnce函数

===frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java ===

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
        String args[];
        Arguments parsedArgs = null;
        FileDescriptor[] descriptors;
        try {
            args = readArgumentList();//读取客户端发送过来的参数
            descriptors = mSocket.getAncillaryFileDescriptors();
        } catch (IOException ex) {
            Log.w(TAG, "IOException on command socket " + ex.getMessage());
            closeSocket();
            return true;
        }
        if (args == null) {
            // EOF reached.
            closeSocket();
            return true;
        }
        /** the stderr of the most recent request, if avail */
        PrintStream newStderr = null;
        if (descriptors != null && descriptors.length >= 3) {
            newStderr = new PrintStream(
                    new FileOutputStream(descriptors[2]));
        }
        int pid = -1;
        FileDescriptor childPipeFd = null;
        FileDescriptor serverPipeFd = null;
        try {
            parsedArgs = new Arguments(args);
            applyUidSecurityPolicy(parsedArgs, peer, peerSecurityContext);
            applyRlimitSecurityPolicy(parsedArgs, peer, peerSecurityContext);
            applyCapabilitiesSecurityPolicy(parsedArgs, peer, peerSecurityContext);
            applyInvokeWithSecurityPolicy(parsedArgs, peer, peerSecurityContext);
            applyseInfoSecurityPolicy(parsedArgs, peer, peerSecurityContext);
            applyDebuggerSystemProperty(parsedArgs);
            applyInvokeWithSystemProperty(parsedArgs);
            int[][] rlimits = null;
            if (parsedArgs.rlimits != null) {
                rlimits = parsedArgs.rlimits.toArray(intArray2d);
            }
            if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
                FileDescriptor[] pipeFds = Libcore.os.pipe();
                childPipeFd = pipeFds[1];
                serverPipeFd = pipeFds[0];
                ZygoteInit.setCloseOnExec(serverPipeFd, true);
            }
            //fork一个新进程
pid=Zygote.forkAndSpecialize(parsedArgs.uid,  parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits,parsedArgs.mountExternal, parsedArgs.seInfo,      parsedArgs.niceName);
        } catch (IOException ex) {
            logAndPrintError(newStderr, "Exception creating pipe", ex);
        } catch (ErrnoException ex) {
            logAndPrintError(newStderr, "Exception creating pipe", ex);
        } catch (IllegalArgumentException ex) {
            logAndPrintError(newStderr, "Invalid zygote arguments", ex);
        } catch (ZygoteSecurityException ex) {
            logAndPrintError(newStderr,
                    "Zygote security policy prevents request: ", ex);
        }
        try {
            if (pid == 0) {//子进程
                // in child
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;
                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
                // should never get here, the child is expected to either
                // throw ZygoteInit.MethodAndArgsCaller or exec().
                return true;
            } else {//父进程
                // in parent...pid of < 0 means failure
                IoUtils.closeQuietly(childPipeFd);
                childPipeFd = null;
                return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
            }
        } finally {
            IoUtils.closeQuietly(childPipeFd);
            IoUtils.closeQuietly(serverPipeFd);
        }
}

===frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java ===

private void handleChildProc(Arguments parsedArgs,
            FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
            throws ZygoteInit.MethodAndArgsCaller {
        closeSocket();//关闭子进程中,从Zygote fork过来的服务端socket
        ZygoteInit.closeServerSocket();
        .....
        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);
        }
        if (parsedArgs.runtimeInit) {
            if(parsedArgs.invokeWith!=null) {WrapperInit.execApplication(parsedArgs.invokeWith,
                        parsedArgs.niceName, parsedArgs.targetSdkVersion,
                        pipeFd, parsedArgs.remainingArgs);
            } else {
                RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs);
            }
        } else {
        	......
        }
    }

===frameworks/base/core/java/com/android/internal/os/RuntimeInit.java ===

    public static final void zygoteInit(int targetSdkVersion, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

        redirectLogStreams();

        commonInit();
        nativeZygoteInit();

        applicationInit(targetSdkVersion, argv);
    }

===frameworks/base/core/java/com/android/internal/os/RuntimeInit.java ==

private static void applicationInit(int targetSdkVersion, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
        // If the application calls System.exit(), terminate the process
        // immediately without running any shutdown hooks.  It is not possible to
        // shutdown an Android application gracefully.  Among other things, the
        // Android runtime shutdown hooks close the Binder driver, which can cause
        // leftover running threads to crash before the process actually exits.
        nativeSetExitWithoutCleanup(true);

        // We want to be fairly aggressive about heap utilization, to avoid
        // holding on to a lot of memory that isn't needed.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

        final Arguments args;
        try {
            args = new Arguments(argv);
        } catch (IllegalArgumentException ex) {
            Slog.e(TAG, ex.getMessage());
            // let the process exit
            return;
        }

        // Remaining arguments are passed to the start class's static main
        invokeStaticMain(args.startClass, args.startArgs);
}

===frameworks/base/core/java/com/android/internal/os/RuntimeInit.java ===

 private static void invokeStaticMain(String className, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
        Class<?> cl;

        try {
            cl = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }
        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
          if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
          }
        /*
         * This throw gets caught in ZygoteInit.main(), which responds
         * by invoking the exception's run() method. This arrangement
         * clears up all the stack frames that were required in setting
         * up the process.
         */
        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
    }

最终是通过反射调用到ActivityThread.java的main函数中。

5. 虚拟机解析dex过程

5.1 前言

学习虚拟机什么时候解析dex,需先了解PMS服务对apk的安装过程。

step 1. 将apk文件复制到data/app目录
step 2. 解析apk信息
step 3. dexopt操作
step 4. 更新权限信息
step 5.完成安装,发送Intent.ACTION_PACKAGE_ADDED广播

而虚拟机对dex文件的解析及优化操作是在第3步中,下面就来看直接来看dexopt的具体实现是怎么做的。

5.2 调用OptMain.cpp过程

=============frameworks/native/cmds/installd/commands.c ==============

int dexopt(const char *apk_path, uid_t uid, int is_public)
{
……
    pid_t pid;
    pid = fork();
    if (pid == 0) {
      ……
        if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {
            run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {
            run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else {
            exit(69);   /* Unexpected persist.sys.dalvik.vm.lib value */
        }
        exit(68);   /* only get here on exec failure */
    } else {
        res = wait_dexopt(pid, apk_path);
        if (res != 0) {
            ALOGE("dexopt in='%s' out='%s' res=%d\n", apk_path, out_path, res);
            goto fail;
        }
    }
……
    return -1;
}

由上面的代码可以发现,installd在做了些操作后,fork出了一个新的进程,根据虚拟机的类型为libdvm或libart分别执行run_dexopt或run_dex2oat

=============frameworks/native/cmds/installd/commands.c ==============

static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX_OPT_BIN = "/system/bin/dexopt";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_num[MAX_INT_LEN];
    char odex_num[MAX_INT_LEN];
    sprintf(zip_num, "%d", zip_fd);
    sprintf(odex_num, "%d", odex_fd);
    ALOGV("Running %s in=%s out=%s\n", DEX_OPT_BIN, input_file_name, output_file_name);
    execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
        dexopt_flags, (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}

上面的代码通过execl函数启动了位于“/system/bin/dexopt”的可执行程序,即位于源码dalvik路径下的OptMain.cpp。

5.3 到关键函数的函数调用

====================dalvik/dexopt/OptMain.cpp ===================

int main(int argc, char* const argv[])
{
    set_process_name("dexopt");
    setvbuf(stdout, NULL, _IONBF, 0);
    if (argc > 1) {
        if (strcmp(argv[1], "--zip") == 0)
            return fromZip(argc, argv);
        else if (strcmp(argv[1], "--dex") == 0)
            return fromDex(argc, argv);
        else if (strcmp(argv[1], "--preopt") == 0)
            return preopt(argc, argv);
    }
    fprintf(stderr,
        "Usage:\n\n"
        "Short version: Don't use this.\n\n"
        "Slightly longer version: This system-internal tool is used to\n"
        "produce optimized dex files. See the source code for details.\n");
    return 1;
}

由于传入的参数是"–zip",所以会走到fromZip函数。

====================dalvik/dexopt/OptMain.cpp ===================

static int fromZip(int argc, char* const argv[])
{
    int result = -1;
    int zipFd, cacheFd;
    const char* zipName;
    char* bcpCopy = NULL;
    const char* dexoptFlags;

    if (argc != 6) {
        ALOGE("Wrong number of args for --zip (found %d)", argc);
        goto bail;
    }
    /* skip "--zip" */
    argc--;
    argv++;
    GET_ARG(zipFd, strtol, "bad zip fd");
    GET_ARG(cacheFd, strtol, "bad cache fd");
    zipName = *++argv;
    --argc;
    dexoptFlags = *++argv;
    --argc;
    result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags);

bail:
    return result;
}

这上面的代码最终要走到processZipFile函数,下面来看这个函数的实现代码。

====================dalvik/dexopt/OptMain.cpp ===================

static int processZipFile(int zipFd, int cacheFd, const char* zipName,
        const char *dexoptFlags)
{
    char* bcpCopy = NULL;
    const char* bcp = getenv("BOOTCLASSPATH");
    if (bcp == NULL) {
        ALOGE("DexOptZ: BOOTCLASSPATH not set");
        return -1;
    }
    bool isBootstrap = false;
    const char* match = strstr(bcp, zipName);
    if (match != NULL) {
        int matchOffset = match - bcp;
        if (matchOffset > 0 && bcp[matchOffset-1] == ':')
            matchOffset--;
        ALOGV("DexOptZ: found '%s' in bootclasspath, cutting off at %d",
            zipName, matchOffset);
        bcpCopy = strdup(bcp);
        bcpCopy[matchOffset] = '\0';

        bcp = bcpCopy;
        ALOGD("DexOptZ: truncated BOOTCLASSPATH to '%s'", bcp);
        isBootstrap = true;
    }
    int result = extractAndProcessZip(zipFd, cacheFd, zipName, isBootstrap,
            bcp, dexoptFlags);
    free(bcpCopy);
    return result;
}

通过上面的一系类的函数调用,走到最终处理apk/jar/zip文件中的classes.dex的函数
extractAndProcessZip。函数实现代码比较长,我们慢慢来看。

====================dalvik/dexopt/OptMain.cpp ===================

static int extractAndProcessZip(int zipFd, int cacheFd,
    const char* debugFileName, bool isBootstrap, const char* bootClassPath,
    const char* dexoptFlagStr)
{
    ZipArchive zippy;
    ZipEntry zipEntry;
    size_t uncompLen;
    long modWhen, crc32;
    off_t dexOffset;
    int err;
    int result = -1;
    int dexoptFlags = 0;        /* bit flags, from enum DexoptFlags */
    DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;
    DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;

    memset(&zippy, 0, sizeof(zippy));

    /* make sure we're still at the start of an empty file */
    if (lseek(cacheFd, 0, SEEK_END) != 0) {
        ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);
        goto bail;
    }

    /*
     * Write a skeletal DEX optimization header.  We want the classes.dex
     * to come just after it.
     */
    err = dexOptCreateEmptyHeader(cacheFd);
    if (err != 0)
        goto bail;

    /* record the file position so we can get back here later */
    dexOffset = lseek(cacheFd, 0, SEEK_CUR);
    if (dexOffset < 0)
        goto bail;

    /*
     * Open the zip archive, find the DEX entry.
     */
    if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {
        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);
        goto bail;
    }

    zipEntry = dexZipFindEntry(&zippy, kClassesDex);
    if (zipEntry == NULL) {
        ALOGW("DexOptZ: zip archive '%s' does not include %s",
            debugFileName, kClassesDex);
        goto bail;
    }

    /*
     * Extract some info about the zip entry.
     */
    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
            &modWhen, &crc32) != 0)
    {
        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
        goto bail;
    }

    uncompLen = uncompLen;
    modWhen = modWhen;
    crc32 = crc32;

    /*
     * Extract the DEX data into the cache file at the current offset.
     */
    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {
        ALOGW("DexOptZ: extraction of %s from %s failed",
            kClassesDex, debugFileName);
        goto bail;
    }

    /* Parse the options. */
    if (dexoptFlagStr[0] != '\0') {
        const char* opc;
        const char* val;

        opc = strstr(dexoptFlagStr, "v=");      /* verification */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;
            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;
            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;
            default:  break;
            }
        }

        opc = strstr(dexoptFlagStr, "o=");      /* optimization */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;
            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;
            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;
            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;
            default:                                            break;
            }
        }

        opc = strstr(dexoptFlagStr, "m=y");     /* register map */
        if (opc != NULL) {
            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
        }

        opc = strstr(dexoptFlagStr, "u=");      /* uniprocessor target */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;
            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;
            default:                                            break;
            }
        }
    }

    /*
     * Prep the VM and perform the optimization.
     */

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
            dexoptFlags) != 0)
    {
        ALOGE("DexOptZ: VM init failed");
        goto bail;
    }

    //vmStarted = 1;

    /* do the optimization */
    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
            modWhen, crc32, isBootstrap))
    {
        ALOGE("Optimization failed");
        goto bail;
    }

    /* we don't shut the VM down -- process is about to exit */

    result = 0;

bail:
    dexZipCloseArchive(&zippy);
    return result;
}

5.4 准备阶段

首先通过dexZipFindEntry()函数检查目标文件中是否拥有class.dex,如果没有就失败返回。

    zipEntry = dexZipFindEntry(&zippy, kClassesDex);
    if (zipEntry == NULL) {
        ALOGW("DexOptZ: zip archive '%s' does not include %s",
            debugFileName, kClassesDex);
        goto bail;
    }

成功的话就调用dexZipGetEntryInfo()函数来读取classes.dex的时间戳与crc校验值。

    /*
     * Extract some info about the zip entry.
     */
    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
            &modWhen, &crc32) != 0)
    {
        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
        goto bail;
    }

如果这一步没有问题,接着调用dexZipExtractEntryToFile()函数释放classes.dex为缓存文件。

    /*
     * Extract the DEX data into the cache file at the current offset.
     */
    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {
        ALOGW("DexOptZ: extraction of %s from %s failed",
            kClassesDex, debugFileName);
        goto bail;
    }

然后开始解析传递过来的验证与优化选项,验证选项使用“v=”指出,优化选项使用“o=”指出。

 /* Parse the options. */
    if (dexoptFlagStr[0] != '\0') {
        const char* opc;
        const char* val;

        opc = strstr(dexoptFlagStr, "v=");      /* verification */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;
            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;
            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;
            default:                                            break;
            }
        }

        opc = strstr(dexoptFlagStr, "o=");      /* optimization */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;
            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;
            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;
            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;
            default:                                            break;
            }
        }

        opc = strstr(dexoptFlagStr, "m=y");     /* register map */
        if (opc != NULL) {
            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
        }

        opc = strstr(dexoptFlagStr, "u=");      /* uniprocessor target */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;
            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;
            default:                                            break;
            }
        }
    }

所有的预备工作都做完后,调用dvmPrepForDexOpt()函数启动一个虚拟机进程,在这个函数中,优化选项dexOptMode与验证选项varifyMode被传递到了全局DvmGlobals结构gDvm的dexOptMode与classVerifyMode字段中。这时候所有的初始化工作已经完成,dexopt调用dvmContinueOptimization()函数开始真正的验证和优化工作。

 /*
     * Prep the VM and perform the optimization.
     */

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
            dexoptFlags) != 0)
    {
        ALOGE("DexOptZ: VM init failed");
        goto bail;
    }

    //vmStarted = 1;

    /* do the optimization */
    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
            modWhen, crc32, isBootstrap))
    {
        ALOGE("Optimization failed");
        goto bail;
    }

5.5 验证阶段

dvmContinueOptimization()函数的调用链比较长。首先从OptMain.cpp转移到/dalvik/vm/analysis/DexPrepare.cpp,因为这里有dvmContinueOptimization()函数的实现。
函数首先对dex文件做简单的检查,确保传递进来的目标文件属于dex或odex。

    /* quick test so we don't blow up on empty file */
    if (dexLength < (int) sizeof(DexHeader)) {
        ALOGE("too small to be DEX");
        return false;
    }
    if (dexOffset < (int) sizeof(DexOptHeader)) {
        ALOGE("not enough room for opt header");
        return false;
    }

    bool result = false;

通过系统调用mmap()函数将整个文件映射到内存中,后面运行app时候可以直接使用dex文件。

       void* mapAddr;
        mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
                    MAP_SHARED, fd, 0);
        if (mapAddr == MAP_FAILED) {
            ALOGE("unable to mmap DEX cache: %s", strerror(errno));
            goto bail;
        }

接着调用rewriteDex()函数来重写dex文件,这里的重写内容包括字符调整、结构重新对齐、类验证信息以及辅助数据。

        /*
         * Rewrite the file.  Byte reordering, structure realigning,
         * class verification, and bytecode optimization are all performed
         * here.
         *
         * In theory the file could change size and bits could shift around.
         * In practice this would be annoying to deal with, so the file
         * layout is designed so that it can always be rewritten in place.
         *
         * This creates the class lookup table as part of doing the processing.
         */
        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
                    doVerify, doOpt, &pClassLookup, NULL);

rewriteDex()函数调用dexSwapAndVerify()调整字节序。

    /* if the DEX is in the wrong byte order, swap it now */
    if (dexSwapAndVerify(addr, len) != 0)
        goto bail;

接着调用dvmDexFileOpenPartial()创建DexFile结构,dvmDexFileOpenPartial()函数的实现在Android系统源码dalvik/vm/DvmDex.cpp文件中。该函数调用dexFileParse()函数解析dex文件,dexFileParse()函数读取dex文件的头部,并根据需要调用dexComputeChecksum()或dexComputeOptChecksum()函数来验证dex或odex文件头的checksum与signature字段。

/*
 * Create a DexFile structure for a "partial" DEX.  This is one that is in
 * the process of being optimized.  The optimization header isn't finished
 * and we won't have any of the auxillary data tables, so we have to do
 * the initialization slightly differently.
 *
 * Returns nonzero on error.
 */
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
    DvmDex* pDvmDex;
    DexFile* pDexFile;
    int parseFlags = kDexParseDefault;
    int result = -1;

    /* -- file is incomplete, new checksum has not yet been calculated
    if (gDvm.verifyDexChecksum)
        parseFlags |= kDexParseVerifyChecksum;
    */

    pDexFile = dexFileParse((u1*)addr, len, parseFlags);
    if (pDexFile == NULL) {
        ALOGE("DEX parse failed");
        goto bail;
    }
    pDvmDex = allocateAuxStructures(pDexFile);
    if (pDvmDex == NULL) {
        dexFileFree(pDexFile);
        goto bail;
    }

    pDvmDex->isMappedReadOnly = false;
    *ppDvmDex = pDvmDex;
    result = 0;

bail:
    return result;
}

继续回到DvmDex.cpp文件继续看代码,当验证成功后,dvmDexFileOpenPartial()函数调用allocateAuxStructures()函数设置DexFile结构辅助数据的相关字段,最后执行完后返回到rewriteDex()函数。rewriteDex()接下来调用loadAllClasses()加载dex文件中所有的类,如果这一步失败了,程序等不到后面的优化与验证就退出了,如果没有错误发生,会调用verifyAndOptimizeClasses()函数进行真正的验证工作,这个函数会调用verifyAndOptimizeClass()函数来优化与验证具体的类,而verifyAndOptimizeClass()函数会细分这些工作,调用dvmVerifyClass()函数进行验证,再调用dvmOptimizeClass()函数进行优化。

dvmVerifyClass()函数的实现代码位于Android系统源码的/dalvik/vm/analysis/DexVerify.cpp文件中。这个函数调用verifyMethod()函数对类的所有直接方法与虚方法进行验证,verifyMethod()函数具体的工作是先调用verifyInstructions()函数来验证方法中的指令及其数据的正确性,再调用dvmVerifyCodeFlow()函数来验证代码流的正确性。

5.6 优化阶段

dvmOptimizeClass()函数的实现代码位于Android系统源码的/dalvik/vm/analysis/Optimize.cpp文件中。这个函数调用optimizeMethod()函数对类的所有直接方法与虚方法进行优化,优化的主要工作是进行“指令替换”,替换原则的优先级为“volatile”替换-正确性替换-高性能替换。比如指令iget-wide会根据优先级替换为“volatile”形式的iget-wide-volatile,而不是高性能的iget-wide-quick.

rewriteDex函数返回后,会再次调用dvmDexFileOpenPartial()来验证odex文件,接着调用dvmGenerateRegisterMaps()函数来填充辅助数据区结构,填充结构完成后,接下来调用updateChecksum()函数重写dex文件的checksum值,再往下就是writeDependencies()与writeOptData()了。

5.7 小结

代码调用流程图如下所示:
在这里插入图片描述

Android虚拟机Dalvik完整源码,宝贵资源,欢迎下载! This directory contains the Dalvik virtual machine and core class library, as well as related tools, libraries, and tests. A note about the licenses and header comments --------------------------------------------- Much of the code under this directory originally came from the Apache Harmony project, and as such contains the standard Apache header comment. Some of the code was written originally for the Android project, and as such contains the standard Android header comment. Some files contain code from both projects. In these cases, the header comment is a combination of the other two, and the portions of the code from Harmony are identified as indicated in the comment. Here is the combined header comment: /* * Copyright (C) The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ---------- * * Portions of the code surrounded by "// BEGIN Harmony code" and * "// END Harmony code" are copyrighted and licensed separately, as * follows: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Native SH call bridge --------------------- Native SH call bridge is written by Shin-ichiro KAWASAKI and Contributed to Android by Hitachi, Ltd. and Renesas Solutions Corp.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值