您的位置:首页 > 其它

关于Davlik解释器的一些思考

2016-03-25 18:24 218 查看
    一直以来对Davlik虚拟机都有一些疑问:
    1、最初被davlik解释执行的java代码是什么呢?
    2、dalvik解释执行java代码时,如果遇到native代码(C/C++)怎么办呢?
    3、如果native代码想调用java代码,davlik是怎么解释执行的呢?
    4、Zygote进程fork出应用程序进程后,davlik是怎么解释执行ActivityThread类的main函数呢?
    5、davlik虚拟机是怎么解释执行java反射代码的呢?
    6、native代码想要调用java代码,只能通过jni机制来让davlik解释执行java代码么?
    7、davlik虚拟机三种加载类的方式是什么?
    8、davlik虚拟机解释执行java代码new instance时,本质发生了什么?

    回答:
    1、最初被davlik解释执行的java代码是com.android.internal.os.ZygoteInit类的静态成员函数main,是通过env->CallStaticVoidMethod(startClass, startMeth, strArray); 这种JNI机制来解释执行的。最后会调用到dvmCallMethodV,如下:
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
......

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)(self->curFrame, pResult, method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}

......
}

    2、答案在dalvik/vm/mterp/out/InterpC-portable.cpp代码中:
GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall,
u2 count, u2 regs)
{
……
if (!dvmIsNativeMethod(methodToCall)) {
/*
* "Call" interpreted code. Reposition the PC, update the
* frame pointer and other local state, and continue.
*/
curMethod = methodToCall;
self->interpSave.method = curMethod;
methodClassDex = curMethod->clazz->pDvmDex;
pc = methodToCall->insns;
fp = newFp;
self->interpSave.curFrame = fp;
#ifdef EASY_GDB
debugSaveArea = SAVEAREA_FROM_FP(newFp);
#endif
self->debugIsMethodEntry = true; // profiling, debugging
ILOGD("> pc <-- %s.%s %s", curMethod->clazz->descriptor,
curMethod->name, curMethod->shorty);
DUMP_REGS(curMethod, fp, true); // show input args
FINISH(0); // jump to method start
} else {
/* set this up for JNI locals, even if not a JNI native */
newSaveArea->xtra.localRefCookie = self->jniLocalRefTable.segmentState.all;

self->interpSave.curFrame = newFp;

DUMP_REGS(methodToCall, newFp, true); // show input args

if (self->interpBreak.ctl.subMode != 0) {
dvmReportPreNativeInvoke(methodToCall, self, newSaveArea->prevFrame);
}

ILOGD("> native <-- %s.%s %s", methodToCall->clazz->descriptor,
methodToCall->name, methodToCall->shorty);

/*
* Jump through native call bridge. Because we leave no
* space for locals on native calls, "newFp" points directly
* to the method arguments.
*/
(*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self);

if (self->interpBreak.ctl.subMode != 0) {
dvmReportPostNativeInvoke(methodToCall, self, newSaveArea->prevFrame);
}

/* pop frame off */
dvmPopJniLocals(self, newSaveArea);
self->interpSave.curFrame = newSaveArea->prevFrame;
fp = newSaveArea->prevFrame;

…….
}
}
assert(false); // should not get here
GOTO_TARGET_END    解释执行的过程中,如果是native方法,那么直接调用nativeFunc去执行。如果不是native方法,那么调到对应method的pc处去解释执行。

    3、参考1问题的回答,是一个env的环境变量调用的,例:env->CallStaticVoidMethod(startClass, startMeth, strArray);最后调用到dvmCallMethodV,如下:
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
......

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)(self->curFrame, pResult, method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}

......
}

    4、Zygote进程fork出应用程序进程时,此时应用程序调用函数的堆栈也和Zygote进程现在的堆栈一样,Android采用的一种抛异常清理堆栈的方法来执行ActivityThread类的main函数。

    5、先看一下执行ActivityThread类的main函数的代码,就是通过反射机制。
public class ZygoteInit {
......

public static class MethodAndArgsCaller extends Exception
implements Runnable {
/** method to call */
private final Method mMethod;

/** argument array */
private final String[] mArgs;

public MethodAndArgsCaller(Method method, String[] args) {
mMethod = method;
mArgs = args;
}

public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });//调用了com.android.server.SystemServer的main函数
} catch (IllegalAccessException ex) {
......
} catch (InvocationTargetException ex) {
......
}
}
}

......
}    


    dvmInvokeMethod代码如下:
Object* dvmInvokeMethod(Object* obj, const Method* method,
ArrayObject* argList, ArrayObject* params, ClassObject* returnType,
bool noAccessCheck)
{
……
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, &retval,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, &retval);
}
……
}

    6、我在看代码的时候发现并不是所有native代码都是通过jni机制,也就是类似于env->CallStaticVoidMethod。也有直接调用dvmCallMethodV,我发现两处:
    之一在findClassFromLoaderNoInit方法中:
const Method* loadClass =
loader->clazz->vtable[gDvm.voffJavaLangClassLoader_loadClass];
JValue result;
dvmCallMethod(self, loadClass, loader, &result, nameObj);    此时要解释执行的java代码是ClassLoader.loadClass。
   
    还一处是创建了davlik线程后davlik来解释执行run方法,参考Dalvik虚拟机进程和线程的创建过程分析
dvmCallMethod(self, run, self->threadObj, &unused);    dvmCallMethod调用了dvmCallMethodV。

    7、三种方式,参考下图:



        显式加载:    (1)ClassLoader.loadClass对应Dalvik_dalvik_system_DexFile_defineClassNative    (2)Class.forName对应Dalvik_java_lang_Class_classForName    隐式加载:    (3)对应dvmResolveClass     第三种方式什么时候调用呢?     在davlik虚拟机解释执行到GOTO_TARGET(invokeVirtual, bool methodCallRange, bool),会执行如下方法:baseMethod = dvmDexGetResolvedMethod(methodClassDex, ref);
if (baseMethod == NULL) {
baseMethod = dvmResolveMethod(curMethod->clazz, ref,METHOD_VIRTUAL);
if (baseMethod == NULL) {
ILOGV("+ unknown method or access denied");
GOTO_exceptionThrown();
}
}    首先通过dvmDexGetResovedMethod来获取本类对象的method,此时classobject已经生成,所以这里使用方法名是ResolvedMethod。    如果该类还没有被加载,那么就要调用dvmResolveMethod首先加载类,然后找到对应的方法,具体实现如下:Method* dvmResolveMethod(const ClassObject* referrer, u4 methodIdx,
MethodType methodType)
{
DvmDex* pDvmDex = referrer->pDvmDex;
ClassObject* resClass;
const DexMethodId* pMethodId;
Method* resMethod;

assert(methodType != METHOD_INTERFACE);

LOGVV("--- resolving method %u (referrer=%s)", methodIdx,
referrer->descriptor);
pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);

resClass = dvmResolveClass(referrer, pMethodId->classIdx, false);

if (methodType == METHOD_DIRECT) {
resMethod = dvmFindDirectMethod(resClass, name, &proto);
} else if (methodType == METHOD_STATIC) {
resMethod = dvmFindDirectMethodHier(resClass, name, &proto);
} else {
resMethod = dvmFindVirtualMethodHier(resClass, name, &proto);
}
……
}    在这个方法中调用如上图中的dvmResolveClass。

    8、无论是通过jni机制,例:env->NewObjectArray(2, stringClass, NULL),还是dalvik解释执行到new instance,本质上都是davlik虚拟机的堆上分配对象,可以垃圾回收。    如果native代码本身创建的对象,分配的空间是操作系统的堆空间,必须手动释放。    9、附上一张Laucher启动应用程序的流程图,方便日后学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: