java dalvik_深入理解Android之Java虚拟机Dalvik
唯一有點(diǎn)特別之處的是常量池。什么東西會(huì)放在常量池呢?最容易想到的就是字符串了。對(duì)頭,這個(gè)Java源碼中的類名,方法名,變量名,居然都是以字符串形式存儲(chǔ)在常量池中。所以,圖2中的this_class和super_class分別指向兩個(gè)字符串,代表本類的名字和基類的名字。這兩個(gè)字符串存儲(chǔ)在常量池中,所以this_class和super_class的類型都是u2(索引,代表長度為2個(gè)字節(jié))。
Class文件用javap工具可以很好得解析成圖2那樣的格式,我這里替大家解析了一把,結(jié)果如圖3所示(先顯示部分內(nèi)容):
注意,解析方法為:javap -verbose xxxx.class
先來看看常量池。
2.1.1 常量池介紹
常量池看起來陌生,其實(shí)簡單得要死。注意,count_pool_count是常量池?cái)?shù)組長度+1。比如,假設(shè)某個(gè)Class文件常量池只有4個(gè)元素,那么count_pool_count=5)。
javap解析class文件的時(shí)候,常量池的索引從1算起,0默認(rèn)是給VM自己用得,一般不顯示0這一項(xiàng)。這也是為什么圖3中常量池第一個(gè)元素以#1開頭。所以,如果count_pool_count=5的話,真正有用的元素是從count_pool[1]到count_pool[4]。
常量池?cái)?shù)組的元素類型由下面的代碼表示:
cp_info { //特別注意,這是介紹的cp_info是相關(guān)元素類型的通用表達(dá)。
u1 tag; //tag為1個(gè)字節(jié)長。不論cp_info具體是哪種,第一個(gè)字節(jié)一定代表tag
u1 info[]; //其他信息,長度隨tag不同而不同
}
//tag取值,先列幾個(gè)簡單的:
tag=7 <==info代表這個(gè)cp_info是CONSTANT_Class_info結(jié)構(gòu)體
tag=9<==info代表CONSTANT_Fieldrefs_info結(jié)構(gòu)體
tag=10<==info代表CONSTANT_Methodrefs_info結(jié)構(gòu)體
tag=8<==info代表CONSTANT_String_info結(jié)構(gòu)體
tag=1<==info代表CONSTANT_Utf8_info結(jié)構(gòu)體
在JVM規(guī)范中,真正代表字符串的數(shù)據(jù)結(jié)構(gòu)是CONSTANT_Utf8_info結(jié)構(gòu)體,它的結(jié)構(gòu)如下代碼所示:
CONSTANT_Utf8_info {
u1 tag;
u2 length; //下面就是存儲(chǔ)UTF8字符串的地方了
u1 bytes[length];
}
大家看圖3中常量池的內(nèi)容,比如#2=Utf8 com/test/TestMain 這行表示:
數(shù)組第二個(gè)元素的類型是CONSTANT_Utf8_info,字符串為“com/test/TestMain”
下面我們看幾個(gè)常用的常量池元素類型
(1) CONSTANT_Class_info
這個(gè)類型是用于描述類信息的,此處的類信息很簡單,就是類名(也就是代表類名的字符串)
CONSTANT_Class_info {
u1 tag; //tag取值為7,代表CONSTANT_Class_info
u2 name_index; //name_index表示代表自己類名的字符串信息位于于常量池?cái)?shù)組中哪一個(gè),也就是索引
}
唉,夠懶的,name_index對(duì)應(yīng)的那個(gè)常量池元素必須是CONSTANT_Utf8_info,也就是字符串。圖3中的例子,咱們?cè)倏纯?#xff1a;
#1 = Class #2 //com/test/TestMain
#2 = Utf8 com/test/TestMain
這說明:
常量池第一個(gè)元素類型為Class_info,它對(duì)應(yīng)的name_index取值為2,表示使用第2個(gè)元素
常量池第二個(gè)元素類型為Utf8 內(nèi)容為“com/test/TestMain”
#1最后的//表示注釋,它把第二行的字符串內(nèi)容直接搬過來,方便我們查看
(2) CONSTANT_NameAndType_Info
這個(gè)結(jié)構(gòu)也是常量池?cái)?shù)據(jù)結(jié)構(gòu)中中比較重要的一個(gè),干什么用得呢?恩,它用來描述方法/成員名以及類型信息的。有點(diǎn)JNI基礎(chǔ)的童鞋相信不難明白,在JNI中,一個(gè)類的成員函數(shù)或成員變量都可以由這個(gè)類名字符串+函數(shù)名字符串+參數(shù)類型字符串+返回值類型來確定(如果是成員變量,就是類名字符串+變量名字符串+類型字符串)來表達(dá)。既然是字符串,那么NameAndType_Info也就是存儲(chǔ)了對(duì)應(yīng)字符串在常量池?cái)?shù)組中的索引:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index; //方法名或域名對(duì)應(yīng)的字符串索引
u2 descriptor_index; //方法信息(參數(shù)+返回值),或者成員變量的信息(類型)對(duì)應(yīng)的字符串索引
}
//還是來看圖3中的例子吧
#13 = Utf8 ()V
#15 = NameAnType #16.#13 //合起來就是test.()V 函數(shù)名是test,參數(shù)和返回值是()V
#16=Utf8 test
太簡單了,都不惜得說...,請(qǐng)大家自行解析#25這個(gè)常量池元素的內(nèi)容,一定要做喔!
注意,對(duì)于構(gòu)造函數(shù)和類初始化函數(shù)來說,JVM要求函數(shù)名必須是和。當(dāng)然,這兩個(gè)函數(shù)是編譯器生成的。
(3) CONSTANT_MethodrefInfo三兄弟
Methodref_Info還有兩個(gè)兄弟,分別是Fieldref_Info,InterfaceMethodref_Info,他們?nèi)糜诿枋龇椒ā⒊蓡T變量和接口信息。剛才的NameAndType_Info其實(shí)已經(jīng)描述了方法和成員變量信息的一部分,唯一還缺的就是沒有地方描述它們屬于哪個(gè)類。而咱這三兄弟就補(bǔ)全了這些信息。他們?nèi)臄?shù)據(jù)結(jié)構(gòu)如圖4所示:
如此直白簡單,不解釋了。不放心的童鞋們請(qǐng)對(duì)照?qǐng)D3的例子自行玩耍!
常量池先介紹到這,它還有一些有用的信息,不過要等到后面我們碰到具體問題時(shí)再分析
2.1.2 Field和Method描述
剛才在常量池介紹中有提到Methodref_Info和Fieldref_Info,不過這兩個(gè)Info無非是描述了函數(shù)或成員變量的名字,參數(shù),類型等信息。但是真正的方法、成員變量信息還包括比如訪問權(quán)限,注解,源代碼位置等。對(duì)于方法來說,更重要的還包括其函數(shù)功能(即這個(gè)函數(shù)對(duì)應(yīng)的字節(jié)碼)。
在Java VM中,方法和成員變量的完整描述由如圖5所示的數(shù)據(jù)結(jié)構(gòu)來表達(dá)的:
access_flags:描述諸如final,static,public這樣的訪問標(biāo)志
name_index:方法或成員變量名在常量池中對(duì)應(yīng)的索引,類型是Utf8_Info
attribute_info:是域或方法中很重要的信息。我們單獨(dú)用一節(jié)來介紹它。
2.1.3 attribute_info介紹
attribute_info結(jié)構(gòu)體很簡單,如下代碼所示:
attribute_info {//特別注意,這里描述的attribute_info結(jié)構(gòu)體也是具體屬性數(shù)據(jù)結(jié)構(gòu)的通用表達(dá)
u2 attribute_name_index; //attribute_info的描述,指向常量池的字符串
u4 attribute_length; //具體的內(nèi)容由info數(shù)組描述
u1 info[attribute_length];
}
Java VM規(guī)范中,attribute類型比較多,我們重點(diǎn)介紹幾個(gè),先來看代表一個(gè)函數(shù)實(shí)際內(nèi)容的Code屬性。
(1) Code屬性
代表Code屬性的數(shù)據(jù)結(jié)構(gòu)如圖6所示:
前2個(gè)成員變量就不多說了。屬于attribute的頭6個(gè)字節(jié),分別指向代表屬性名字符串的常量池元素以及后續(xù)屬性數(shù)據(jù)的長度。注意,Code屬性的attribute_name_index所指向的那個(gè)Utf8常量池元素對(duì)應(yīng)的字符串內(nèi)容就是“Code”,大家可參考圖3的#9。
max_stack和max_locals:虛擬機(jī)在執(zhí)行一個(gè)函數(shù)的時(shí)候,會(huì)為它建立一個(gè)操作數(shù)棧。執(zhí)行過程中的參數(shù)啊,一些計(jì)算值啊等都會(huì)壓入棧中。max_stack就表示該函數(shù)執(zhí)行時(shí),這個(gè)棧的最大深度。這是編譯時(shí)就能確定的。max_locals用于描述這個(gè)方法最大的棧數(shù)和最大的本地變量個(gè)數(shù)。本地變量個(gè)數(shù)包括傳入的參數(shù)。
code_length和code:這個(gè)函數(shù)編譯成Java字節(jié)碼后對(duì)應(yīng)的字節(jié)碼長度和內(nèi)容。
exception_table_length:用來描述該方法對(duì)應(yīng)異常處理的信息。這塊我不打算講了,其實(shí)也蠻簡單,就是用start_pc表示異常處理時(shí)候從此方法對(duì)應(yīng)字節(jié)碼(由code[]數(shù)組表示)哪個(gè)地方開始執(zhí)行。
Code屬性本身還能包含一些屬性,這是由attributes_count和attributes數(shù)組決定的。
來看個(gè)實(shí)際例子吧,如圖7所示(接著圖3的例子):
圖7中:
stack=2,locals=2,args_size=1。結(jié)合代碼,main函數(shù)確實(shí)有一個(gè)參數(shù),而且還有一個(gè)本地變量。注意,main函數(shù)是static的。如果對(duì)于類的非static函數(shù),那么locals的第0個(gè)元素代表this。
stack后面接下來的就是code數(shù)組,也就是這個(gè)函數(shù)對(duì)應(yīng)的執(zhí)行代碼。0表示code[]的索引位置。0:new:代表這個(gè)操作是new操作,此操作對(duì)應(yīng)的字節(jié)碼長度為3,所以下一個(gè)操作對(duì)應(yīng)的字節(jié)碼從索引3開始。
LineNumberTable也是屬性的一種,用于調(diào)試,它將源碼和字節(jié)碼匹配了起來。比如line 7: 0這句話代表該函數(shù)字節(jié)碼0那一個(gè)操作對(duì)應(yīng)代碼的第7行。
LocalVariableTable:它也是屬性一種,用于調(diào)試,它用于描述函數(shù)執(zhí)行時(shí)的變量信息。比如圖7中的Start = 0:表示從code[]第0個(gè)字節(jié)開始,Length = 13表示到從start=0到start+13個(gè)字節(jié)(不包含第13個(gè)字節(jié),因?yàn)閏ode數(shù)組一共就12個(gè)字節(jié))這段范圍內(nèi),這個(gè)變量都有效(也就是這個(gè)變量的作用域),Slot=0表示這個(gè)變量在本地變量表中第一個(gè)元素,還記得前面提到的locals嗎?,name為“args”,表示這個(gè)參數(shù)的名字叫args,類型(由Signature表示)就是String數(shù)組了。
請(qǐng)大家自行解析圖7中最后一行,看看能搞明白LocalVariableTable的含義不...
另外,Android SDK build Tools中的dx工具dump class文件得到的信息更全,大家可以試試。
使用方法是:dx --dump --debug xxx.class。
Class文件先介紹到這,下面我們來看看Android平臺(tái)上的dex文件。
2.2 Dex文件結(jié)構(gòu)和Odex
2.2.1 dex文件結(jié)構(gòu)簡介
Android平臺(tái)中沒有直接使用Class文件格式,因?yàn)樵缙诘腁nrdroid手機(jī)內(nèi)存,存儲(chǔ)都比較小,而Class文件顯然有很多可以優(yōu)化的地方,比如每個(gè)Class文件都有一個(gè)常量池,里邊存儲(chǔ)了一些字符串。一串內(nèi)容完全相同的字符串很有可能在不同的Class文件的常量池中存在,這就是一個(gè)可以優(yōu)化的地方。當(dāng)然,Dex文件結(jié)構(gòu)和Class文件結(jié)構(gòu)差異的地方還很多,但是從攜帶的信息上來看,Dex和Class文件是一致的。所以,你了解了Class文件(作為Java VM官方Spec的標(biāo)準(zhǔn)),Dex文件結(jié)構(gòu)只不過是一個(gè)變種罷了(從學(xué)習(xí)到什么程度為止的問題來看,如果不是要自己來解析Dex文件,或者反編譯/修改dex文件,我覺得大致了解下Dex文件結(jié)構(gòu)的情況就可以了)。圖8所示為Dex文件結(jié)構(gòu)的概貌:
有一點(diǎn)需要說明:傳統(tǒng)Class文件是一個(gè)Java源碼文件會(huì)生成一個(gè).Class文件,而Android是把所有Class文件進(jìn)行合并,優(yōu)化,然后生成一個(gè)最終的class.dex,如此,多個(gè)Class文件里如果有重復(fù)的字符串,當(dāng)把它們都放到一個(gè)dex文件的時(shí)候,只要一份就可以了嘛。
dex頭部信息中的magic取值為“dexn035 ”
proto_ids:描述函數(shù)原型信息,包括返回值,參數(shù)信息。比如“test:()V”
methods_ids:函數(shù)信息,包括所屬類及對(duì)應(yīng)的proto信息。比如
"Lcom.test.TestMain. test:()V",.前面是類信息,后面屬于proto信息
下面我們將示例TestMain.class轉(zhuǎn)換成dex文件,然后再用dexdump工具看看它的結(jié)果,如圖9所示:
具體方法:
總結(jié)
以上是生活随笔為你收集整理的java dalvik_深入理解Android之Java虚拟机Dalvik的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 中调用docker_如何通过J
- 下一篇: java动态创建bean的意义_java