java dalvik-ag凯发k8国际
唯一有点特别之处的是常量池。什么东西会放在常量池呢?最容易想到的就是字符串了。对头,这个java源码中的类名,方法名,变量名,居然都是以字符串形式存储在常量池中。所以,图2中的this_class和super_class分别指向两个字符串,代表本类的名字和基类的名字。这两个字符串存储在常量池中,所以this_class和super_class的类型都是u2(索引,代表长度为2个字节)。
class文件用javap工具可以很好得解析成图2那样的格式,我这里替大家解析了一把,结果如图3所示(先显示部分内容):
注意,解析方法为:javap -verbose xxxx.class
先来看看常量池。
2.1.1 常量池介绍
常量池看起来陌生,其实简单得要死。注意,count_pool_count是常量池数组长度 1。比如,假设某个class文件常量池只有4个元素,那么count_pool_count=5)。
javap解析class文件的时候,常量池的索引从1算起,0默认是给vm自己用得,一般不显示0这一项。这也是为什么图3中常量池第一个元素以#1开头。所以,如果count_pool_count=5的话,真正有用的元素是从count_pool[1]到count_pool[4]。
常量池数组的元素类型由下面的代码表示:
cp_info { //特别注意,这是介绍的cp_info是相关元素类型的通用表达。
u1 tag; //tag为1个字节长。不论cp_info具体是哪种,第一个字节一定代表tag
u1 info[]; //其他信息,长度随tag不同而不同
}
//tag取值,先列几个简单的:
tag=7 <==info代表这个cp_info是constant_class_info结构体
tag=9<==info代表constant_fieldrefs_info结构体
tag=10<==info代表constant_methodrefs_info结构体
tag=8<==info代表constant_string_info结构体
tag=1<==info代表constant_utf8_info结构体
在jvm规范中,真正代表字符串的数据结构是constant_utf8_info结构体,它的结构如下代码所示:
constant_utf8_info {
u1 tag;
u2 length; //下面就是存储utf8字符串的地方了
u1 bytes[length];
}
大家看图3中常量池的内容,比如#2=utf8 com/test/testmain 这行表示:
数组第二个元素的类型是constant_utf8_info,字符串为“com/test/testmain”
下面我们看几个常用的常量池元素类型
(1) constant_class_info
这个类型是用于描述类信息的,此处的类信息很简单,就是类名(也就是代表类名的字符串)
constant_class_info {
u1 tag; //tag取值为7,代表constant_class_info
u2 name_index; //name_index表示代表自己类名的字符串信息位于于常量池数组中哪一个,也就是索引
}
唉,够懒的,name_index对应的那个常量池元素必须是constant_utf8_info,也就是字符串。图3中的例子,咱们再看看:
#1 = class #2 //com/test/testmain
#2 = utf8 com/test/testmain
这说明:
常量池第一个元素类型为class_info,它对应的name_index取值为2,表示使用第2个元素
常量池第二个元素类型为utf8 内容为“com/test/testmain”
#1最后的//表示注释,它把第二行的字符串内容直接搬过来,方便我们查看
(2) constant_nameandtype_info
这个结构也是常量池数据结构中中比较重要的一个,干什么用得呢?恩,它用来描述方法/成员名以及类型信息的。有点jni基础的童鞋相信不难明白,在jni中,一个类的成员函数或成员变量都可以由这个类名字符串 函数名字符串 参数类型字符串 返回值类型来确定(如果是成员变量,就是类名字符串 变量名字符串 类型字符串)来表达。既然是字符串,那么nameandtype_info也就是存储了对应字符串在常量池数组中的索引:
constant_nameandtype_info {
u1 tag;
u2 name_index; //方法名或域名对应的字符串索引
u2 descriptor_index; //方法信息(参数 返回值),或者成员变量的信息(类型)对应的字符串索引
}
//还是来看图3中的例子吧
#13 = utf8 ()v
#15 = nameantype #16.#13 //合起来就是test.()v 函数名是test,参数和返回值是()v
#16=utf8 test
太简单了,都不惜得说...,请大家自行解析#25这个常量池元素的内容,一定要做喔!
注意,对于构造函数和类初始化函数来说,jvm要求函数名必须是和。当然,这两个函数是编译器生成的。
(3) constant_methodrefinfo三兄弟
methodref_info还有两个兄弟,分别是fieldref_info,interfacemethodref_info,他们三用于描述方法、成员变量和接口信息。刚才的nameandtype_info其实已经描述了方法和成员变量信息的一部分,唯一还缺的就是没有地方描述它们属于哪个类。而咱这三兄弟就补全了这些信息。他们三的数据结构如图4所示:
如此直白简单,不解释了。不放心的童鞋们请对照图3的例子自行玩耍!
常量池先介绍到这,它还有一些有用的信息,不过要等到后面我们碰到具体问题时再分析
2.1.2 field和method描述
刚才在常量池介绍中有提到methodref_info和fieldref_info,不过这两个info无非是描述了函数或成员变量的名字,参数,类型等信息。但是真正的方法、成员变量信息还包括比如访问权限,注解,源代码位置等。对于方法来说,更重要的还包括其函数功能(即这个函数对应的字节码)。
在java vm中,方法和成员变量的完整描述由如图5所示的数据结构来表达的:
access_flags:描述诸如final,static,public这样的访问标志
name_index:方法或成员变量名在常量池中对应的索引,类型是utf8_info
attribute_info:是域或方法中很重要的信息。我们单独用一节来介绍它。
2.1.3 attribute_info介绍
attribute_info结构体很简单,如下代码所示:
attribute_info {//特别注意,这里描述的attribute_info结构体也是具体属性数据结构的通用表达
u2 attribute_name_index; //attribute_info的描述,指向常量池的字符串
u4 attribute_length; //具体的内容由info数组描述
u1 info[attribute_length];
}
java vm规范中,attribute类型比较多,我们重点介绍几个,先来看代表一个函数实际内容的code属性。
(1) code属性
代表code属性的数据结构如图6所示:
前2个成员变量就不多说了。属于attribute的头6个字节,分别指向代表属性名字符串的常量池元素以及后续属性数据的长度。注意,code属性的attribute_name_index所指向的那个utf8常量池元素对应的字符串内容就是“code”,大家可参考图3的#9。
max_stack和max_locals:虚拟机在执行一个函数的时候,会为它建立一个操作数栈。执行过程中的参数啊,一些计算值啊等都会压入栈中。max_stack就表示该函数执行时,这个栈的最大深度。这是编译时就能确定的。max_locals用于描述这个方法最大的栈数和最大的本地变量个数。本地变量个数包括传入的参数。
code_length和code:这个函数编译成java字节码后对应的字节码长度和内容。
exception_table_length:用来描述该方法对应异常处理的信息。这块我不打算讲了,其实也蛮简单,就是用start_pc表示异常处理时候从此方法对应字节码(由code[]数组表示)哪个地方开始执行。
code属性本身还能包含一些属性,这是由attributes_count和attributes数组决定的。
来看个实际例子吧,如图7所示(接着图3的例子):
图7中:
stack=2,locals=2,args_size=1。结合代码,main函数确实有一个参数,而且还有一个本地变量。注意,main函数是static的。如果对于类的非static函数,那么locals的第0个元素代表this。
stack后面接下来的就是code数组,也就是这个函数对应的执行代码。0表示code[]的索引位置。0:new:代表这个操作是new操作,此操作对应的字节码长度为3,所以下一个操作对应的字节码从索引3开始。
linenumbertable也是属性的一种,用于调试,它将源码和字节码匹配了起来。比如line 7: 0这句话代表该函数字节码0那一个操作对应代码的第7行。
localvariabletable:它也是属性一种,用于调试,它用于描述函数执行时的变量信息。比如图7中的start = 0:表示从code[]第0个字节开始,length = 13表示到从start=0到start 13个字节(不包含第13个字节,因为code数组一共就12个字节)这段范围内,这个变量都有效(也就是这个变量的作用域),slot=0表示这个变量在本地变量表中第一个元素,还记得前面提到的locals吗?,name为“args”,表示这个参数的名字叫args,类型(由signature表示)就是string数组了。
请大家自行解析图7中最后一行,看看能搞明白localvariabletable的含义不...
另外,android sdk build tools中的dx工具dump class文件得到的信息更全,大家可以试试。
使用方法是:dx --dump --debug xxx.class。
class文件先介绍到这,下面我们来看看android平台上的dex文件。
2.2 dex文件结构和odex
2.2.1 dex文件结构简介
android平台中没有直接使用class文件格式,因为早期的anrdroid手机内存,存储都比较小,而class文件显然有很多可以优化的地方,比如每个class文件都有一个常量池,里边存储了一些字符串。一串内容完全相同的字符串很有可能在不同的class文件的常量池中存在,这就是一个可以优化的地方。当然,dex文件结构和class文件结构差异的地方还很多,但是从携带的信息上来看,dex和class文件是一致的。所以,你了解了class文件(作为java vm官方spec的标准),dex文件结构只不过是一个变种罢了(从学习到什么程度为止的问题来看,如果不是要自己来解析dex文件,或者反编译/修改dex文件,我觉得大致了解下dex文件结构的情况就可以了)。图8所示为dex文件结构的概貌:
有一点需要说明:传统class文件是一个java源码文件会生成一个.class文件,而android是把所有class文件进行合并,优化,然后生成一个最终的class.dex,如此,多个class文件里如果有重复的字符串,当把它们都放到一个dex文件的时候,只要一份就可以了嘛。
dex头部信息中的magic取值为“dexn035 ”
proto_ids:描述函数原型信息,包括返回值,参数信息。比如“test:()v”
methods_ids:函数信息,包括所属类及对应的proto信息。比如
"lcom.test.testmain. test:()v",.前面是类信息,后面属于proto信息
下面我们将示例testmain.class转换成dex文件,然后再用dexdump工具看看它的结果,如图9所示:
具体方法:
总结
以上是ag凯发k8国际为你收集整理的java dalvik_深入理解android之java虚拟机dalvik的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇:
- 下一篇: java动态创建bean的意义_java