欢迎访问 生活随笔!

凯发k8官方网

当前位置: 凯发k8官方网 > 运维知识 > android >内容正文

android

java dalvik-凯发k8官方网

发布时间:2024/10/14 android 7 豆豆
凯发k8官方网 收集整理的这篇文章主要介绍了 java dalvik_深入理解android之java虚拟机dalvik 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

唯一有点特别之处的是常量池。什么东西会放在常量池呢?最容易想到的就是字符串了。对头,这个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所示:

具体方法:

总结

以上是凯发k8官方网为你收集整理的java dalvik_深入理解android之java虚拟机dalvik的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得凯发k8官方网网站内容还不错,欢迎将凯发k8官方网推荐给好友。

网站地图