Dalvik虚拟机java方法执行流程和Method结构体分析
Method结构体是啥?
在Dalvik虚拟机内部,每个Java方法都有一个对应的Method结构体,虚拟机根据此结构体获取方法的所有信息.
Method结构体是怎样定义的?
此结构体在不同的android版本稍有变化,但是结构体前面比较重要的一部分(从clazz到nativeFunc)完全没有变化.以下是android4.4.2_r2的Method结构体定义(位于/dalvik/vm/oo/Object.h).
1 /* 2 * A method. We create one of these for every method in every class 3 * we load, so try to keep the size to a minimum. 4 * 5 * Much of this comes from and could be accessed in the data held in shared 6 * memory. We hold it all together here for speed. Everything but the 7 * pointers could be held in a shared table generated by the optimizer; 8 * if we're willing to convert them to offsets and take the performance 9 * hit (e.g. "meth->insns" becomes "baseAddr + meth->insnsOffset") we 10 * could move everything but "nativeFunc". 11 */ 12 struct Method { 13 /* the class we are a part of */ 14 ClassObject*clazz; 15 16 /* access flags; low 16 bits are defined by spec (could be u2?) */ 17 u4 accessFlags; 18 19 /* 20 * For concrete virtual methods, this is the offset of the method 21 * in "vtable". 22 * 23 * For abstract methods in an interface class, this is the offset 24 * of the method in "iftable[n]->methodIndexArray". 25 */ 26 u2 methodIndex; 27 28 /* 29 * Method bounds; not needed for an abstract method. 30 * 31 * For a native method, we compute the size of the argument list, and 32 * set "insSize" and "registerSize" equal to it. 33 */ 34 u2 registersSize; /* ins + locals */ 35 u2 outsSize; 36 u2 insSize; 37 38 /* method name, e.g. "<init>" or "eatLunch" */ 39 const char* name; 40 41 /* 42 * Method prototype descriptor string (return and argument types). 43 * 44 * TODO: This currently must specify the DexFile as well as the proto_ids 45 * index, because generated Proxy classes don't have a DexFile. We can 46 * remove the DexFile* and reduce the size of this struct if we generate 47 * a DEX for proxies. 48 */ 49 DexProtoprototype; 50 51 /* short-form method descriptor string */ 52 const char* shorty; 53 54 /* 55 * The remaining items are not used for abstract or native methods. 56 * (JNI is currently hijacking "insns" as a function pointer, set 57 * after the first call. For internal-native this stays null.) 58 */ 59 60 /* the actual code */ 61 const u2* insns; /* instructions, in memory-mapped .dex */ 62 63 /* JNI: cached argument and return-type hints */ 64 int jniArgInfo; 65 66 /* 67 * JNI: native method ptr; could be actual function or a JNI bridge. We 68 * don't currently discriminate between DalvikBridgeFunc and 69 * DalvikNativeFunc; the former takes an argument superset (i.e. two 70 * extra args) which will be ignored. If necessary we can use 71 * insns==NULL to detect JNI bridge vs. internal native. 72 */ 73 DalvikBridgeFunc nativeFunc; 74 75 /* 76 * JNI: true if this static non-synchronized native method (that has no 77 * reference arguments) needs a JNIEnv* and jclass/jobject. Libcore 78 * uses this. 79 */ 80 bool fastJni; 81 82 /* 83 * JNI: true if this method has no reference arguments. This lets the JNI 84 * bridge avoid scanning the shorty for direct pointers that need to be 85 * converted to local references. 86 * 87 * TODO: replace this with a list of indexes of the reference arguments. 88 */ 89 bool noRef; 90 91 /* 92 * JNI: true if we should log entry and exit. This is the only way 93 * developers can log the local references that are passed into their code. 94 * Used for debugging JNI problems in third-party code. 95 */ 96 bool shouldTrace; 97 98 /* 99 * Register map data, if available. This will point into the DEX file 100 * if the data was computed during pre-verification, or into the 101 * linear alloc area if not. 102 */ 103 const RegisterMap* registerMap; 104 105 /* set if method was called during method profiling */ 106 boolinProfile; 107 };
1 struct Method { 2 ClassObject* clazz; 3 u4 accessFlags; 4 u2 methodIndex; 5 6 u2 registersSize; 7 u2 outsSize; 8 u2 insSize; 9 10 const char* name; 11 DexProto prototype; 12 const char* shorty; 13 const u2* insns; 14 15 int jniArgInfo; 16 DalvikBridgeFunc nativeFunc; 17 18 bool fastJni; 19 bool noRef; 20 bool shouldTrace; 21 const RegisterMap* registerMap; 22 bool inProfile; 23 };
第二个图更容易看
native层的两种引用对象类型
一种是普通的JNI方式,特点是引用对象类型为jobject等样式.第二种就是Dalvik虚拟机内部使用的引用对象类型(Object*,ClassObject*等样式).在虚拟机内部有若干个表存储jobject与Object*的对应关系,因此两者在虚拟机内部可以相互转换.
Dalvik虚拟机眼中的java方法分类
在Dalvik虚拟机看来,java方法分为三类:
1.普通的java方法,即由java代码实现的方法.
2.通过JNI函数实现的native方法,典型的是声明为native的方法.特点是输入参数中的引用对象类型为jobject类型.
3.虚拟机内部实现的native方法.特点是输入参数中的引用对象类型为Object*,ClassObject*等类型.
Dalvik虚拟机是如何执行一个方法的?
以dvmCallMethod为例,其主要执行流程如下图
结论:Method结构体重要成员的意义
结合以上Dalvik虚拟机方法执行流程和对Android源码的分析,得到Method结构体中几个重要成员的意义如下
accessFlags 各个不同标志位表示此方法的多个属性,其中标志位0x00000100表明此方法是native的.
registersSize 该方法总共用到的寄存器个数,包含输入参数所用到的寄存器,还有方法内部另外使用到的寄存器,在调用方法时会为其申请栈内存.
outsSize 该方法调用其他方法时使用到的寄存器个数,注意:只有此方法为非native方法时,此值才有效.
insSize 该方法输入参数用到的寄存器个数(registersSize包含此值)
insns 若方法类型为1,这里指向实际的字节码首地址;若方法类型为2,这里指向实际的JNI函数首地址;若方法类型为3,这里为null.
jniArgInfo 当方法类型为2时有效,记录了一些预先计算好的信息(具体信息格式与实际CPU架构有关,但总是包含返回值类型),从而不需要在调用的时候再通过方法的参数和返回值实时计算了,提高了JNI调用的速度。如果第一位为1(即0x80000000),则Dalvik虚拟机会忽略后面的所有信息,强制在调用时实时计算.
nativeFunc 若方法类型为1,此值无效;若方法类型为2,这里指向dvmCallJNIMethod;若方法类型为3,这里指向实际的处理函数(DalvikBridgeFunc类型).
附录:其余执行方法的函数
void dvmCallMethodA(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, const jvalue* args) 功能与dvmCallMethodV类似,只是参数格式不同而已.
Object* dvmInvokeMethod(Object* obj, const Method* method, ArrayObject* argList, ArrayObject* params, ClassObject* returnType, bool noAccessCheck) 特点在于会根据method的实际输入参数类型argList将输入参数params中的包装类型解包为实际需要的基本类型,并且如果实际返回类型为基本类型它会将结果打包为对应的包装类型返回
void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self)
这里其实就是一个普通的DalvikBridgeFunc函数,当某个方法为JNI实现的native方法时,该方法对应的Method结构体中的nativeFun就指向此函数
它的主要功能就是将Object*样式的引用类型的输入参数转换为JNI格式的jobject样式的引用之后调用dvmPlatformInvokevoid dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc,
const u4* argv, const char* shorty, void* func, JValue* pReturn)执行时取决于具体的CPU架构,部分采用汇编实现.对于参数argInfo,有的实现确实能够加速方法调用速度(mips,new ARM),有的必须保证argInfo有效(386),有的会忽略(old ARM).
参考资料