摘要:對(duì)一個(gè)文件的字節(jié)碼進(jìn)行逐行的分析是理解文件結(jié)構(gòu)的最佳方式。本文的目的在于盡可能完整地拆解的字節(jié)碼并將其分塊分析,最終得到的圖解結(jié)構(gòu)希望可以幫助到你。字節(jié)碼指令的具體含義鑒于與結(jié)構(gòu)是相對(duì)獨(dú)立的主題不再詳述,后續(xù)會(huì)再多帶帶深入介紹。
對(duì)一個(gè)class文件的字節(jié)碼進(jìn)行逐行的分析是理解class文件結(jié)構(gòu)的最佳方式。但是往往復(fù)雜的二進(jìn)制字節(jié)碼會(huì)讓人望而卻步,或者只有仔細(xì)一點(diǎn)點(diǎn)盯著才能保證不花眼。本文的目的在于盡可能完整地拆解JVM的Class字節(jié)碼并將其分塊分析,最終得到的圖解結(jié)構(gòu)希望可以幫助到你。
本文參考自來(lái)自周志明《深入理解Java虛擬機(jī)(第2版)》,拓展內(nèi)容建議讀者可以閱讀下這本書。根據(jù)這個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明
以下的例子作為最簡(jiǎn)單的一個(gè)java程序,通過(guò)javac執(zhí)行編譯,javap來(lái)查看它的反編譯結(jié)果,當(dāng)然我們還會(huì)更刨根問(wèn)底地直接使用二進(jìn)制編輯器查看class文件的二進(jìn)制字節(jié)排布。
> javap -v Test Classfile /Users/jinhaoplus/Desktop/Test.class Last modified 2018-8-12; size 285 bytes MD5 checksum eac8f02f8ad176b09bfd89cf15e2ed3d Compiled from "Test.java" public class top.jinhaoplus.demo.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#15 // java/lang/Object."圖解概況":()V #2 = Fieldref #3.#16 // top/jinhaoplus/demo/Test.m:I #3 = Class #17 // top/jinhaoplus/demo/Test #4 = Class #18 // java/lang/Object #5 = Utf8 m #6 = Utf8 I #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 inc #12 = Utf8 ()I #13 = Utf8 SourceFile #14 = Utf8 Test.java #15 = NameAndType #7:#8 // " ":()V #16 = NameAndType #5:#6 // m:I #17 = Utf8 top/jinhaoplus/demo/Test #18 = Utf8 java/lang/Object { public int m; descriptor: I flags: ACC_PUBLIC public top.jinhaoplus.demo.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: return LineNumberTable: line 3: 0 public int inc(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field m:I 4: iconst_1 5: iadd 6: ireturn LineNumberTable: line 6: 0 } SourceFile: "Test.java"
如上的字節(jié)碼閱讀起來(lái)有諸多障礙,因此我們把上面的字節(jié)碼按照字節(jié)碼規(guī)范定義的class結(jié)構(gòu)分區(qū)為不同的顏色塊,不同的分區(qū)顏色說(shuō)明這個(gè)區(qū)域?qū)?yīng)著class結(jié)構(gòu)中的不同區(qū)域定義,表示一個(gè)整體概念的字節(jié)碼在圖中顯示為同一行上:
下面的圖是class文件結(jié)構(gòu)的思維導(dǎo)圖說(shuō)明,可以跟上述的實(shí)際的一個(gè)class的分區(qū)作簡(jiǎn)單的對(duì)照:
詳細(xì)解釋一下class文件的每個(gè)分區(qū)下面詳細(xì)解釋一下class文件的每個(gè)分區(qū),括號(hào)內(nèi)的數(shù)字表示當(dāng)前區(qū)的占位情況,u是字節(jié)的意思,如u4表示占4個(gè)字節(jié)的空間,對(duì)應(yīng)到圖中就是4個(gè)方格。
1. magicmagic(u4):魔數(shù),class文件的標(biāo)識(shí)開(kāi)頭。
CAFEBABE是固定的JVM Class的魔數(shù),也可以認(rèn)為是眾所周知的Java咖啡Logo的由來(lái)。
2. versionversion:class版本,主次版本合起來(lái)即可確定版本號(hào)。
2.1 minor_version(u2):次版本
2.2 major_version(u2):主版本
Class文件的版本為次版本0X0000、主版本0X0034,對(duì)應(yīng)的是10進(jìn)制的52.0。說(shuō)明此Class是在JDK_VERSION=52.0(JDK1.8)的編譯器中生成的,同時(shí)又可以被版本在JDK_VERSION=52.0及以上的虛擬機(jī)上執(zhí)行(JVM保持了向下兼容性,但是拒絕執(zhí)行超過(guò)它的版本號(hào)的Class字節(jié)碼)。
3. 常量池:注意是本處的常量池指class字節(jié)碼中的常量池而非JVM中的常量池(但后者中的數(shù)據(jù)其實(shí)是加載于前者)。 3.1 constant_pool_countconstant_pool_count(u2):常量池大小,定義了常量池中保存的常量個(gè)數(shù)(準(zhǔn)確說(shuō)常量個(gè)數(shù)=constant_pool_count-1)。
0X0013表示constant_pool_count=19,常量池中保存的常量個(gè)數(shù)=18(編號(hào)為#1~#18)。
3.2 constant_poolconstant_pool(constant_pool_count-1個(gè)constant_pool_info):實(shí)際保存的常量,編號(hào)從1開(kāi)始(將0位留空有特殊考量)。
常量有多種種類,我們這里只提一下我們的Class文件里涉及到的具體的類型。
由utf-8編碼的二進(jìn)制串,其字節(jié)碼格式為
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
其中的tag=0X01即為CONSTANT_Utf8_info類型常量的標(biāo)識(shí)。我們Class字節(jié)碼中的#5、#6、#7、#8、#9、#10、#11、#12、#13、#14、#17、#18都是CONSTANT_Utf8_info常量,因?yàn)樗鼈兊氖孜籺ag=0X01(橘色列),通過(guò)utf-8解碼這些常量指定長(zhǎng)度的二進(jìn)制串可以得出下面的結(jié)果,比如#5號(hào)常量length=1(10進(jìn)制的0X0001),而bytes為0X6D,utf-8解碼后就是字符串m,同理可以得到這些二進(jìn)制串的值(這就是javap反編譯出結(jié)果的原理,可以參照javap得到的結(jié)果對(duì)照一下):
#5 m #6 I #7#8 ()V #9 Code #10 LineNumberTable #11 inc #12 ()I #13 SourceFile #14 Test.java #17 top/jinhaoplus/demo/Test #18 java/lang/Object
類常量,其字節(jié)碼格式為
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | index | 1 |
其中的tag=0X07即為CONSTANT_Class_info類型常量的標(biāo)識(shí),index指向了常量池中類的全限定名的索引序號(hào)。
我們Class字節(jié)碼中的#3、#4是CONSTANT_Class_info類型的類常量,它們的首位tag=0X07(橘色列),通過(guò)查找常量池中它們指向的索引序號(hào),我們可以得出這兩個(gè)類的全限定名:
#3 #17 // top/jinhaoplus/demo/Test #4 #18 // java/lang/Object
字段或方法的名稱和類型常量,其字節(jié)碼格式為
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | index | 1 |
u2 | index | 1 |
其中的tag=0X0C即為CONSTANT_NameAndType_info類型常量的標(biāo)識(shí),第一個(gè)index指向了字段或方法名稱在常量池中的索引序號(hào),第二個(gè)index指向了字段或方法的描述符在常量池中的索引序號(hào)。
字段的描述符就是簡(jiǎn)單的字段類型,Class文件中的類型為了節(jié)省空間進(jìn)行了簡(jiǎn)化:如基本類型int->I,double->D,引用類型java/lang/Object -> Ljava/lang/Object。
我們Class字節(jié)碼中的#15、#16是CONSTANT_NameAndType_info類型的類常量,它們的首位tag=0X0C(橘色列),通過(guò)查找常量池中它們兩個(gè)指向的索引序號(hào),我們可以得出常量#15的名稱為#7號(hào)常量即
#15 #7:#8 // "":()V #16 #5:#6 // m:I
字段引用常量,其字節(jié)碼格式為
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | index | 1 |
u2 | index | 1 |
其中的tag=0X09即為CONSTANT_Fieldref_info類型常量的標(biāo)識(shí),第一個(gè)index指向了聲明字段的類或接口的CONSTANT_Class_info常量在常量池中的索引序號(hào),第二個(gè)index指向了字段的名稱和類型信息CONSTANT_NameAndType_info常量在常量池中的索引序號(hào)。
我們Class字節(jié)碼中的#2是CONSTANT_Fieldref_info類型的類常量,它們的首位tag=0X09(橘色列),通過(guò)查找常量池中它指向的索引序號(hào),我們可以得出這個(gè)字段的聲明類的是top/jinhaoplus/demo/Test,字段的名稱是m,類型是I(即int,Class將類型全稱映射到成了單字母)。
#2 #3.#16 // top/jinhaoplus/demo/Test.m:I
方法引用常量,其字節(jié)碼格式為
類型 | 名稱 | 數(shù)量 |
---|---|---|
u1 | tag | 1 |
u2 | index | 1 |
u2 | index | 1 |
其中的tag=0X0A即為CONSTANT_Methodref_info類型常量的標(biāo)識(shí),第一個(gè)index指向了聲明方法的類或接口的CONSTANT_Class_info常量在常量池中的索引序號(hào),第二個(gè)index指向了方法的名稱和類型信息CONSTANT_NameAndType_info常量在常量池中的索引序號(hào)。
我們Class字節(jié)碼中的#1是CONSTANT_Fieldref_info類型的類常量,它們的首位tag=0X0A(橘色列),通過(guò)查找常量池中它指向的索引序號(hào),我們可以得出這個(gè)方法的聲明類是java/lang/Object,方法的名稱是
#1 #4.#15 // java/lang/Object."":()V
至此我們得到了這個(gè)Class中的常量池中全部的常量的含義。這些常量將被下面的其他部分引用到。
4.類信息: 4.1 access_flagaccess_flag(u2):說(shuō)明這個(gè)類或接口的訪問(wèn)標(biāo)志,如private/public/interface/abstract/annotation/enum等,總之是說(shuō)明了這個(gè)類的特征。以不同的特征給出特征位的方式來(lái)設(shè)置這個(gè)u2大小的區(qū)域。
如本Class的0X0021實(shí)際代表了特征位信息是0000000000110001,即ACC_SUPER|ACC_PUBLIC,表示它是public的class(ACC_SUPER是JDK1.0.2后的默認(rèn)設(shè)置項(xiàng))。
this_class(u2):說(shuō)明本類的類索引,0X0003說(shuō)明本類索引在常量池中的序號(hào)為3,上面常量池的分析可以看到本類的全限定名是top/jinhaoplus/demo/Test。
4.3 super_classsuper_class(u2):說(shuō)明父類的類索引,0X0004說(shuō)明父類索引在常量池中的序號(hào)為4,上面常量池的分析可以看到父類的全限定名是java/lang/Object(這也就是所有Java類的父類都是Object的原因,即使沒(méi)有明確寫出來(lái)編譯后的Class文件中也會(huì)將這個(gè)父類聲明定義出來(lái))。
4.4 interface_info類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flag | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
比如private/public/protected/static/final/volatile,以不同的特征給出特征位的方式來(lái)設(shè)置這個(gè)u2大小的區(qū)域。我們的Class中的0X0001(橙色列)實(shí)際代表了特征位信息是0000000000000001,即字段的特征是ACC_PUBLIC(public字段)。
我們的Class中的0X0005(藍(lán)色列)指向的常量池中的#5號(hào)常量即m。
我們的Class中的0X0006(青色列)指向的常量池中的#6號(hào)常量即I。
5.2.4.1 attributes_count(u2):字段屬性表的屬性數(shù)量,我們的Class中的0X0000表示本字段無(wú)額外的屬性表信息。
5.2.4.1 attributes(attributes_count個(gè)attribute_info):字段屬性表的屬性信息,字段屬性有自己定義的結(jié)構(gòu),字段中主要使用的屬性包括ConstantValue(final修飾的常量值作為字段的值)、Depreciated(@Depreciated修飾的字段表示棄用)、Signature(泛型參數(shù)記錄的泛型簽名信息,否則編譯后擦除類型就無(wú)法溯源了)等,他們都有各自定義的結(jié)構(gòu)。
6.方法信息 6.1 method_countmethod_count(u2):方法數(shù)量
我們的Class這個(gè)區(qū)的0X0002表示這個(gè)類有兩個(gè)方法。
method_info(method_count個(gè)method_info):方法信息,方法表的結(jié)構(gòu)如下:
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flag | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
比如private/public/protected/static/final/synchronized,以不同的特征給出特征位的方式來(lái)設(shè)置這個(gè)u2大小的區(qū)域。我們的Class中兩個(gè)方法的這個(gè)區(qū)域的0X0001(橙色列)實(shí)際代表了特征位信息是0000000000000001,即它們的特征都是ACC_PUBLIC(public方法)。
我們的Class中,method_#1的0X0005(藍(lán)色列)指向的常量池中的#7號(hào)常量即
我們的Class中,method_#1的0X0008(青色列)指向的常量池中的#8號(hào)常量即()V,而。method_#2的0X000C(青色列)指向的常量池中的#12號(hào)常量即()I。
6.2.4.1 attributes_count(u2):方法屬性表的屬性數(shù)量。
6.2.4.2 attributes(attributes_count個(gè)attribute_info):方法屬性表的屬性信息,方法屬性有自己定義的結(jié)構(gòu),方法中主要使用的屬性包括最重要的Code(方法的字節(jié)碼指令,沒(méi)有方法執(zhí)行體的接口和抽象類是沒(méi)有這個(gè)屬性的)、Exceptions(聲明方法拋出的異常)、Depreciated(@Depreciated修飾的方法表示棄用)、Signature(泛型參數(shù)記錄的泛型簽名信息)等,他們都有各自定義的結(jié)構(gòu)。這里我們具體來(lái)看一下最重要的Code屬性。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attribute_count | 1 |
attribute_info | attributes | attribute_count |
a. attribute_name_index(u2):屬性名在常量池中的索引序號(hào),Code屬性最終找到的常量肯定是Code。
b. attribute_length(u4):該屬性的長(zhǎng)度。
c. max_stack(u2):該方法的操作數(shù)棧最大深度。
d. max_locals(u2):該方法的局部變量表的大小。
e. code_length(u4):字節(jié)碼指令的大小
f. code(exception_table_length個(gè)u1):字節(jié)碼。
g. exception_table_length(u2):異常表大小。
h. exception_table(exception_table_length個(gè)exception_info):異常表大小。
i. attributes_count(u2):方法屬性表的大小。
j. attributes(attribute_count個(gè)attribute_info):方法屬性表。
接下來(lái)用我們Class的兩個(gè)方法來(lái)詳細(xì)說(shuō)明Code屬性:
method_#1方法:
i. attributes_count = 1
ii. attributes:
a. attribute_name_index:常量0X0009即Code。
b. attribute_length:29(0X0000001D),即下一位起后的29u都是這個(gè)屬性。
c. max_stack:1(0X0001)。
d. max_locals:1(0X0001)。
e. code_length:5(0X00000005)。
f. code:0X2AB70001B1。(字節(jié)碼指令的具體含義鑒于與class結(jié)構(gòu)是相對(duì)獨(dú)立的主題不再詳述,后續(xù)會(huì)再多帶帶深入介紹)
g. exception_table_length:0(OX0000)。
h. exception_table:無(wú)。
i. attributes_count:1(0X0001)。
j. attributes:
attribute_name_index:LineNumberTable(0X000A),說(shuō)明這是一個(gè)用于記錄源碼行號(hào)和字節(jié)碼行號(hào)映射的屬性表。
attribute_length:6(0X00000006).
attribute:LineNumberTable屬性表的內(nèi)部結(jié)構(gòu):
line_number_table_length:1(0X0001)。
line_number_index:0:3(0X00000003)。
method_#2方法的分析方式如上類似不再贅述。
Class字節(jié)碼的結(jié)構(gòu)為什么這么設(shè)計(jì)乍一看來(lái)上面的結(jié)構(gòu)讓人很難快速理解,但是如果理解JVM的字節(jié)碼結(jié)構(gòu)的設(shè)計(jì)目的就可以加深理解了。
JVM的字節(jié)碼結(jié)構(gòu)其實(shí)是一種由字節(jié)碼堆砌的表型結(jié)構(gòu),充分定義占位的結(jié)構(gòu)可以無(wú)歧義地將它想要表達(dá)的原義還原回去。作為二進(jìn)制結(jié)構(gòu)主要的表達(dá)方式,只要定義好占位情況,表型結(jié)構(gòu)可以通過(guò)層層嵌套定義來(lái)實(shí)現(xiàn)更為復(fù)雜的結(jié)構(gòu)、并且可以實(shí)現(xiàn)良好的拓展。
比如上面的介紹的方法信息通過(guò)方法數(shù)量定義了這個(gè)表的大小,而每個(gè)表entry內(nèi)部可以再有自己的定義,比如方法信息中還可以包含屬性表(即在方法表內(nèi)部再嵌套一層表),比如這里定義了Code屬性表,而Code屬性表自身又有良好的表結(jié)構(gòu)定義,這個(gè)表內(nèi)部除了一些一維的字段(比如index、count等不能拓展的字段)外,還有額外的exception_table,但是因?yàn)橛衑xception_table_length的表大小限制就可以無(wú)歧義地還原回去,此外還有attribute_info,但是因?yàn)橛衋ttribute_count的表大小限制也可以無(wú)歧義地還原回去。用下面的思維導(dǎo)圖我們可以直觀地看出來(lái)這種良好的定義,圖中加入了每個(gè)一維節(jié)點(diǎn)的占位大小:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/76736.html
摘要:請(qǐng)注意,我們?cè)诹牧膯卧獪y(cè)試遇到問(wèn)題多思考多查閱多驗(yàn)證,方能有所得,再勤快點(diǎn)樂(lè)于分享,才能寫出好文章。單元測(cè)試是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。 JAVA容器-自問(wèn)自答學(xué)HashMap 這次我和大家一起學(xué)習(xí)HashMap,HashMap我們?cè)诠ぷ髦薪?jīng)常會(huì)使用,而且面試中也很頻繁會(huì)問(wèn)到,因?yàn)樗锩嫣N(yùn)含著很多知識(shí)點(diǎn),可以很好的考察個(gè)人基礎(chǔ)。但一個(gè)這么重要的東西,我為什么沒(méi)有在一開(kāi)始...
摘要:不同于個(gè)人面經(jīng),這份面經(jīng)具有普適性。我在前面的文章中也提到了應(yīng)該怎么做自我介紹與項(xiàng)目介紹,詳情可以查看這篇文章備戰(zhàn)春招秋招系列初出茅廬的程序員該如何準(zhǔn)備面試。是建立連接時(shí)使用的握手信號(hào)。它表示確認(rèn)發(fā)來(lái)的數(shù)據(jù)已經(jīng)接受無(wú)誤。 showImg(https://segmentfault.com/img/remote/1460000016972448?w=921&h=532); 該文已加入開(kāi)源文...
摘要:阻塞,非阻塞首先,阻塞這個(gè)詞來(lái)自操作系統(tǒng)的線程進(jìn)程的狀態(tài)模型網(wǎng)絡(luò)爬蟲(chóng)基本原理一后端掘金網(wǎng)絡(luò)爬蟲(chóng)是捜索引擎抓取系統(tǒng)的重要組成部分。每門主要編程語(yǔ)言現(xiàn)未來(lái)已到后端掘金使用和在相同環(huán)境各加載多張小圖片,性能相差一倍。 2016 年度小結(jié)(服務(wù)器端方向)| 掘金技術(shù)征文 - 后端 - 掘金今年年初我花了三個(gè)月的業(yè)余時(shí)間用 Laravel 開(kāi)發(fā)了一個(gè)項(xiàng)目,在此之前,除了去年換工作準(zhǔn)備面試時(shí),我并...
摘要:阻塞,非阻塞首先,阻塞這個(gè)詞來(lái)自操作系統(tǒng)的線程進(jìn)程的狀態(tài)模型網(wǎng)絡(luò)爬蟲(chóng)基本原理一后端掘金網(wǎng)絡(luò)爬蟲(chóng)是捜索引擎抓取系統(tǒng)的重要組成部分。每門主要編程語(yǔ)言現(xiàn)未來(lái)已到后端掘金使用和在相同環(huán)境各加載多張小圖片,性能相差一倍。 2016 年度小結(jié)(服務(wù)器端方向)| 掘金技術(shù)征文 - 后端 - 掘金今年年初我花了三個(gè)月的業(yè)余時(shí)間用 Laravel 開(kāi)發(fā)了一個(gè)項(xiàng)目,在此之前,除了去年換工作準(zhǔn)備面試時(shí),我并...
摘要:外部存儲(chǔ)器可用于長(zhǎng)期保存大量程序和數(shù)據(jù),其成本低容量大,但速度較慢。 1_計(jì)算機(jī)概述(了解) A:什么是計(jì)算機(jī)?計(jì)算機(jī)在生活中的應(yīng)用舉例 計(jì)算機(jī)(Computer)全稱:電子計(jì)算機(jī),俗稱電腦。是一種能夠按照程序運(yùn)行,自動(dòng)、高速處理海量數(shù)據(jù)的現(xiàn)代化智能電子設(shè)備。由硬件和軟件所組成,沒(méi)有安裝任何軟件的計(jì)算機(jī)稱為裸機(jī)。常見(jiàn)的形式有臺(tái)式計(jì)算機(jī)、筆記本計(jì)算機(jī)、大型計(jì)算機(jī)等。 應(yīng)用舉例 ...
閱讀 3822·2023-04-25 18:41
閱讀 1286·2021-11-11 16:55
閱讀 1911·2021-09-22 15:54
閱讀 3145·2021-09-22 15:51
閱讀 3609·2019-08-30 15:55
閱讀 2006·2019-08-30 14:19
閱讀 1396·2019-08-29 10:57
閱讀 1774·2019-08-29 10:56