// 本文涉及的源文件:
system/core/init/init.cpp
system/core/rootdir/init.rc
system/core/rootdir/init.zygote64.rc
framework/cmds/app_process/app_main.cpp
framework/core/jni/AndroidRuntime.cpp
libnativehelper/JniInvocation.cpp
dalvik2/vm/Jni.cpp
dalvik2/vm/init.cpp
dalvik2/vm/InitRefs.cpp
dalvik2/vm/oo/Class.cpp
dalvik2/vm/interp/Stack.cpp
dalvik2/vm/interp/Interp.cpp
dalvik2/vm/mterp/out/InterpC-portable.cpp
art/runtime/jni/java_vm_ext.cc
概要
本文主要从源码的角度,分析Android虚拟机是如何启动并创建java世界的,包括虚拟机的创建、第一个Java类的加载、代码的执行。
看下文之前,不妨先思考几个问题:
- android虚拟机是谁创建的?
- android虚拟机的java入口类是哪个?
- 虚拟机是如何找到入口类的?
- 一个类从加载到执行都有哪些步骤?
- 类静态方法块是何时开始执行的?
天字第一号进程init进程启动后,会解析init.rc中的脚本,然后按照特定顺序依次执行其中的命令。init.rc中引用了init.zygote64.rc,其中有条命令如下:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
这条命令会调用app_process64启动zygote。
app_process64对应的源文件为framework/cmds/app_process/app_main.cpp。
//framework/cmds/app_process/app_main.cpp
int main(int argc, char *argv[]) {
...
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
...
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
}
创建AppRuntime,然后启动“com.android.internal.os.ZygoteInit”类。
//framework/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote) {
...
JniInvocation jni_invocation;
jni_invocation.Init(NULL); //初始化jni调用
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote) != 0) { //启动虚拟机
return;
}
onVmCreated(env);
...
//找到要启动的java类,然后调用其main方法
jclass startClass = env->FindClass(slashClassName);
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
主要流程是:
- 初始化Jni调用
- 启动虚拟机
- 找到要启动的java类
- 执行main方法
android系统在4.4以前是dalvik虚拟机,之后增加了art虚拟机,为了对上层使用屏蔽虚拟机的不同实现,通过JniInvocation对其进行封装。dalvi和art都实现了同样的虚拟机接口,并编译成了动态链接库so,JNIInvocation.Init的作用就是根据参数选择一个so,然后找到其中创建虚拟机的方法。这其实就是面向对象中继承多态的思想。
JniInvocation.Init执行完,会给三个函数指针赋值,分别是
- JNI_GetDefaultJavaVMInitArgs_
- JNI_CreateJavaVM_
- JNI_GetCreatedJavaVMs_
然后我们看一下dalvik虚拟机是如何实现JNI_CreateJavaVM的。
//dalvik2/vm/Jni.cpp
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
...
JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = NULL;
...
gDvmJni.jniVm = (JavaVM*) pVM;
JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
//启动虚拟机
dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
}
主要就是创建虚拟机对象JavaVMExt和Jni环境对象JNIEnvExt,然后启动虚拟机。
创建的过程就是为其中各种函数指针赋值,比如FindClass、NewObject等,这些都是java语法的实现。
启动虚拟机比较复杂,有垃圾回收、native调用、类相关的初始化工作。。
//dalvik2/vm/init.cpp
dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv) {
...
dvmAllocTrackerStartup();
dvmGcStartup();
dvmThreadStartup();
dvmInlineNativeStartup;
...
dvmClassStartup();
dvmFindRequiredClassesAndMembers();
dvmStringInternStartup();
..
}
上面的dvmClassStartup会创建class对象以及基础数据类型对象(Void、Boolean、Byte等)
dvmFindRequiredClassesAndMembers,调用initClassReferences,该方法会初始化一些java语言的一些基本类比如Object、Throwable、Class、ClassLoader、String,以及反射相关的类。初始化方法为:
static bool initClassReference(ClassObject** pClass, const char* name) {
ClassObject* result;
if (name[0] == '[') {
result = dvmFindArrayClass(name, NULL);
} else {
result = dvmFindSystemClassNoInit(name);
}
...
*pClass = result;
return true;
}
对于数组则调用dvmFindArrayClass,否则调用dvmFindSystemClassNoInit
//dalvik2/vm/oo/Class.cpp
ClassObject* dvmFindSystemClassNoInit(const char* descriptor)
{
return findClassNoInit(descriptor, NULL, NULL);
}
static ClassObject* findClassNoInit(const char* descriptor, Object* loader,
DvmDex* pDvmDex)
{
clazz = dvmLookupClass(descriptor, loader, true);
if (clazz == NULL) {
if (pDvmDex == NULL) {
pDvmDex = searchBootPathForClass(descriptor, &pClassDef);
} else {
pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
}
...
clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
...
dvmAddClassToHash(clazz);
...
dvmLinkClass(clazz)
...
return clazz;
}
}
上面的查找类的过程是带缓存的,如果缓存中存在则直接返回,否则需要执行查找、加载、链接的过程,然后将类添加到缓存中。
- 类的查找
- 系统启动类
系统类需要根据环境变量BOOTCLASSPATH指定的路径去查找,BOOTCLASSPATH是在init.environ.rc文件中设置的,可以在shell中查看。
capricorn:/ $ echo $BOOTCLASSPATH
/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/apache-xml.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/tcmiface.jar:/system/framework/telephony-ext.jar:/system/framework/WfdCommon.jar:/system/framework/oem-services.jar:/system/framework/qcom.fmradio.jar:/system/framework/qcmediaplayer.jar:/system/app/miui/miui.apk:/system/app/miuisystem/miuisystem.apk
通过遍历BOOTCLASSPATH中的所有jar包或apk,找到第一个包含指定类的。
- 普通类
普通类则去指定的dex文件中去查找。
- 类的加载
类加载就是根据dex中描述的类的信息,创建对应的c++对象ClassObject。
主要包括父类信息、接口信息、类变量、成员变量、直接方法和虚方法。需要注意的是,此时只是记录了父类在dex中的索引,并没有加载父类的详细信息,详细信息需要链接的时候才去做。 - 类的链接
首先递归的对父类和实现的接口进行解析。
然后创建虚方法表,接口方法表,创建一些特殊目的的方法桩。
计算对象的大小(需要考虑变量的地址对齐),方便以后分配内存。
此时我们已经找到了ZygoteInit类,接下来获取其中的main方法。
// dalvik2/vm/Jni.cpp
static jmethodID GetStaticMethodID(JNIEnv* env, jclass jclazz, const char* name, const char* sig) {
if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
assert(dvmCheckException(ts.self()));
return NULL;
}
...
}
在获取静态方法时,如果发现类还没有初始化,则调用dvmInitClass进行初始化。
// dalvik2/vm/oo/Class.cpp
bool dvmInitClass(ClassObject* clazz) {
...
dvmVerifyClass(clazz);
...
if (!IS_CLASS_FLAG_SET(clazz, CLASS_ISOPTIMIZED) && !gDvm.optimizing) {
dvmOptimizeClass(clazz);
}
...
if (clazz->super != NULL && clazz->super->status != CLASS_INITIALIZED) {
dvmInitClass(clazz->super);
}
...
initSFields(clazz);
...
method = dvmFindDirectMethodByDescriptor(clazz, "", "()V");
dvmCallMethod(self, method, NULL, &unused);
}
主要包括四步:
- verify验证
验证主要是对方法的字节码进行静态安全检查, - optimizie优化
主要是对字节码进行优化,用一些更高效的指令替换旧的指令。 - 初始化类静态变量
- 执行类初始化代码
类初始化之后,则可以进行方法调用了。
//dalvik2/vm/interp/Stack.cpp
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args) {
...
//准备工作:创建栈帧
clazz = callPrep(self, method, obj, false);
// 如果不是静态方法,则将this当做参数入栈
if (!dvmIsStaticMethod(method)) {
#ifdef WITH_EXTRA_OBJECT_VALIDATION
assert(obj != NULL && dvmIsHeapAddress(obj));
#endif
*ins++ = (u4) obj;
verifyCount++;
}
// 其他参数入栈
...
//native方法使用nativeFunc指针
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
//解释执行
dvmInterpret(self, method, pResult);
}
//出栈
dvmPopFrame(self);
}
指令的解释执行如下
// dalvik2/vm/interp/Interp.cpp
void dvmInterpret(Thread* self, const Method* method, JValue* pResult) {
...
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit ||
gDvm.executionMode == kExecutionModeNcgO0 ||
gDvm.executionMode == kExecutionModeNcgO1)
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretPortable;
// Call the interpreter
(*stdInterp)(self);
*pResult = self->interpSave.retval;
}
dalvik有fast、jit、protable三种执行模式。
- fast会针对本地平台进行优化,可以更快速的执行Java代码
- jit会将Java代码动态编译为Native代码,然后执行
- protable则是可移植模式,也就是通用模式
如果想看某个指令的具体实现,便可以去相应模式的解释器去看代码,比如new instance的实现。
// dalvik2/vm/mterp/out/InterpC-portable.cpp
HANDLE_OPCODE(OP_NEW_INSTANCE /*vAA, class@BBBB*/)
{
...
clazz = dvmDexGetResolvedClass(methodClassDex, ref);
if (clazz == NULL) {
clazz = dvmResolveClass(curMethod->clazz, ref, false);
}
if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz))
GOTO_exceptionThrown();
...
newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
if (newObj == NULL)
GOTO_exceptionThrown();
SET_REGISTER(vdst, (u4) newObj);
}
FINISH(2);
OP_END
代码很清晰,先从缓存查找,是否已经解析过该类,如果没有则去解析。解析完,如果没有初始化,则对类进行初始化,最后为对象分配内存。
结束
本文只是粗略的给出了虚拟机从创建到执行的一个条主线,很多细节没有深入,比如虚拟机的内存布局、垃圾回收、线程机制、jni的实现、指令优化等等。不过有了这条主线,对于代码研究是个很好的开头,接下来我会继续研究JNI的实现原理。
ps:现在你可以回答一下开头的问题了。