JVM详解之:java class文件的密码本
文章目錄
- 簡(jiǎn)介
- 一個(gè)簡(jiǎn)單的class
- ClassFile的二進(jìn)制文件
- class文件的密碼本
- magic
- version
- 常量池
- 描述符
- access_flags
- this_class和super_class
- interfaces_count和interfaces[]
- fields_count和fields[]
- methods_count和methods[]
- attributes_count和attributes[]
- 總結(jié)
簡(jiǎn)介
一切的一切都是從javac開(kāi)始的。從那一刻開(kāi)始,java文件就從我們?nèi)庋劭煞直娴奈谋疚募?#xff0c;變成了冷冰冰的二進(jìn)制文件。
變成了二進(jìn)制文件是不是意味著我們無(wú)法再深入的去了解java class文件了呢?答案是否定的。
機(jī)器可以讀,人為什么不能讀?只要我們掌握java class文件的密碼表,我們可以把二進(jìn)制轉(zhuǎn)成十六進(jìn)制,將十六進(jìn)制和我們的密碼表進(jìn)行對(duì)比,就可以輕松的解密了。
下面,讓我們開(kāi)始這個(gè)激動(dòng)人心的過(guò)程吧。
一個(gè)簡(jiǎn)單的class
為了深入理解java class的含義,我們首先需要定義一個(gè)class類:
public class JavaClassUsage {private int age=18;public void inc(int number){this.age=this.age+ number;} }很簡(jiǎn)單的類,我想不會(huì)有比它更簡(jiǎn)單的類了。
在上面的類中,我們定義了一個(gè)age字段和一個(gè)inc的方法。
接下來(lái)我們使用javac來(lái)進(jìn)行編譯。
IDEA有沒(méi)有?直接打開(kāi)編譯后的class文件,你會(huì)看到什么?
沒(méi)錯(cuò),是反編譯過(guò)來(lái)的java代碼。但是這次我們需要深入了解的是class文件,于是我們可以選擇 view->Show Bytecode:
當(dāng)然,還是少不了最質(zhì)樸的javap命令:
javap -verbose JavaClassUsage對(duì)比會(huì)發(fā)現(xiàn),其實(shí)javap展示的更清晰一些,我們暫時(shí)選用javap的結(jié)果。
編譯的class文件有點(diǎn)長(zhǎng),我一度有點(diǎn)不想都列出來(lái),但是又一想只有對(duì)才能講述得更清楚,還是貼在下面:
public class com.flydean.JavaClassUsageminor version: 0major version: 58flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #2.#3 // java/lang/Object."<init>":()V#2 = Class #4 // java/lang/Object#3 = NameAndType #5:#6 // "<init>":()V#4 = Utf8 java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I#8 = Class #10 // com/flydean/JavaClassUsage#9 = NameAndType #11:#12 // age:I#10 = Utf8 com/flydean/JavaClassUsage#11 = Utf8 age#12 = Utf8 I#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 LocalVariableTable#16 = Utf8 this#17 = Utf8 Lcom/flydean/JavaClassUsage;#18 = Utf8 inc#19 = Utf8 (I)V#20 = Utf8 number#21 = Utf8 SourceFile#22 = Utf8 JavaClassUsage.java {public com.flydean.JavaClassUsage();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: bipush 187: putfield #7 // Field age:I10: returnLineNumberTable:line 7: 0line 9: 4LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/flydean/JavaClassUsage;public void inc(int);descriptor: (I)Vflags: ACC_PUBLICCode:stack=3, locals=2, args_size=20: aload_01: aload_02: getfield #7 // Field age:I5: iload_16: iadd7: putfield #7 // Field age:I10: returnLineNumberTable:line 12: 0line 13: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/flydean/JavaClassUsage;0 11 1 number I } SourceFile: "JavaClassUsage.java"ClassFile的二進(jìn)制文件
慢著,上面javap的結(jié)果好像并不是二進(jìn)制文件!
對(duì)的,javap是對(duì)二進(jìn)制文件進(jìn)行了解析,方便程序員閱讀。如果你真的想直面最最底層的機(jī)器代碼,就直接用支持16進(jìn)制的文本編譯器把編譯好的class文件打開(kāi)吧。
你準(zhǔn)備好了嗎?
來(lái)吧,展示吧!
上圖左邊是16進(jìn)制的class文件代碼,右邊是對(duì)16進(jìn)制文件的適當(dāng)解析。大家可以隱約的看到一點(diǎn)點(diǎn)熟悉的內(nèi)容。
是的,沒(méi)錯(cuò),你會(huì)讀機(jī)器語(yǔ)言了!
class文件的密碼本
如果你要了解class文件的結(jié)構(gòu),你需要這個(gè)密碼本。
如果你想解析class文件,你需要這個(gè)密碼本。
學(xué)好這個(gè)密碼本,走遍天下都…沒(méi)啥用!
下面就是密碼本,也就是classFile的結(jié)構(gòu)。
ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];u2 fields_count;field_info fields[fields_count];u2 methods_count;method_info methods[methods_count];u2 attributes_count;attribute_info attributes[attributes_count]; }其中u2,u4表示的是無(wú)符號(hào)的兩個(gè)字節(jié),無(wú)符號(hào)的4個(gè)字節(jié)。
java class文件就是按照上面的格式排列下來(lái)的,按照這個(gè)格式,我們可以自己實(shí)現(xiàn)一個(gè)反編譯器(大家有興趣的話,可以自行研究)。
我們對(duì)比著上面的二進(jìn)制文件一個(gè)一個(gè)的來(lái)理解。
magic
首先,class文件的前4個(gè)字節(jié)叫做magic word。
看一下十六進(jìn)制的第一行的前4個(gè)字節(jié):
CA FE BA BE 00 00 00 3A 00 17 0A 00 02 00 03 070xCAFEBABE就是magic word。所有的java class文件都是以這4個(gè)字節(jié)開(kāi)頭的。
來(lái)一杯咖啡吧,baby!
多么有詩(shī)意的畫(huà)面。
version
這兩個(gè)version要連著講,一個(gè)是主版本號(hào),一個(gè)是次版本號(hào)。
00 00 00 3A對(duì)比一下上面的表格,我們的主版本號(hào)是3A=58,也就是我們使用的是JDK14版本。
常量池
接下來(lái)是常量池。
首先是兩個(gè)字節(jié)的constant_pool_count。對(duì)比一下,constant_pool_count的值是:
00 17換算成十進(jìn)制就是23。也就是說(shuō)常量池的大小是23-1=22。
這里有兩點(diǎn)要注意,第一點(diǎn),常量池?cái)?shù)組的index是從1開(kāi)始到constant_pool_count-1結(jié)束。
第二點(diǎn),常量池?cái)?shù)組的第0位是作為一個(gè)保留位,表示“不引用任何常量池項(xiàng)目”,為某些特殊的情況下使用。
接下來(lái)是不定長(zhǎng)度的cp_info:constant_pool[constant_pool_count-1]常量池?cái)?shù)組。
常量池?cái)?shù)組中存了些什么東西呢?
字符串常量,類和接口名字,字段名,和其他一些在class中引用的常量。
具體的constant_pool中存儲(chǔ)的常量類型有下面幾種:
每個(gè)常量都是以一個(gè)tag開(kāi)頭的。用來(lái)告訴JVM,這個(gè)到底是一個(gè)什么常量。
好了,我們對(duì)比著來(lái)看一下。在constant_pool_count之后,我們?cè)偃∫徊糠?6進(jìn)制數(shù)據(jù):
上面我們講到了17是常量池的個(gè)數(shù),接下來(lái)就是常量數(shù)組。
0A 00 02 00 03首先第一個(gè)字節(jié)是常量的tag, 0A=10,對(duì)比一下上面的表格,10表示的是CONSTANT_Methodref方法引用。
CONSTANT_Methodref又是一個(gè)結(jié)構(gòu)體,我們?cè)倏匆幌路椒ㄒ玫亩x:
CONSTANT_Methodref_info {u1 tag;u2 class_index;u2 name_and_type_index; }從上面的定義我們可以看出,CONSTANT_Methodref是由三部分組成的,第一部分是一個(gè)字節(jié)的tag,也就是上面的0A。
第二部分是2個(gè)字節(jié)的class_index,表示的是類在常量池中的index。
第三部分是2個(gè)字節(jié)的name_and_type_index,表示的是方法的名字和類型在常量池中的index。
先看class_index,0002=2。
常量池的第一個(gè)元素我們已經(jīng)找到了就是CONSTANT_Methodref,第二個(gè)元素就是跟在CONSTANT_Methodref后面的部分,我們看下是什么:
07 00 04一樣的解析步驟,07=7,查表,表示的是CONSTANT_Class。
我們?cè)倏聪翪ONSTANT_Class的定義:
CONSTANT_Class_info {u1 tag;u2 name_index; }可以看到CONSTANT_Class占用3個(gè)字節(jié),第一個(gè)字節(jié)是tag,后面兩個(gè)字節(jié)是name在常量池中的索引。
00 04 = 4, 表示name在常量池中的索引是4。
然后我們就這樣一路找下去,就得到了所有常量池中常量的信息。
這樣找起來(lái),眼睛都花了,有沒(méi)有什么簡(jiǎn)單的辦法呢?
當(dāng)然有,就是上面的javap -version, 我們?cè)倩仡櫼幌螺敵鼋Y(jié)果中的常量池部分:
Constant pool:#1 = Methodref #2.#3 // java/lang/Object."<init>":()V#2 = Class #4 // java/lang/Object#3 = NameAndType #5:#6 // "<init>":()V#4 = Utf8 java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I#8 = Class #10 // com/flydean/JavaClassUsage#9 = NameAndType #11:#12 // age:I#10 = Utf8 com/flydean/JavaClassUsage#11 = Utf8 age#12 = Utf8 I#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 LocalVariableTable#16 = Utf8 this#17 = Utf8 Lcom/flydean/JavaClassUsage;#18 = Utf8 inc#19 = Utf8 (I)V#20 = Utf8 number#21 = Utf8 SourceFile#22 = Utf8 JavaClassUsage.java以第一行為例,直接告訴你常量池中第一個(gè)index的類型是Methodref,它的classref是index=2,它的NameAndType是index=3。
并且直接在后面展示出了具體的值。
描述符
且慢,在常量池中我好像看到了一些不一樣的東西,這些I,L是什么東西?
這些叫做字段描述符:
上圖是他們的各項(xiàng)含義。除了8大基礎(chǔ)類型,還有2個(gè)引用類型,分別是對(duì)象的實(shí)例,和數(shù)組。
access_flags
常量池后面就是access_flags:訪問(wèn)描述符,表示的是這個(gè)class或者接口的訪問(wèn)權(quán)限。
先上密碼表:
再找一下我們16進(jìn)制的access_flag:
沒(méi)錯(cuò),就是00 21。 參照上面的表格,好像沒(méi)有21,但是別怕:
21是ACC_PUBLIC和ACC_SUPER的并集。表示它有兩個(gè)access權(quán)限。
this_class和super_class
接下來(lái)是this class和super class的名字,他們都是對(duì)常量池的引用。
00 08 00 02this class的常量池index=8, super class的常量池index=2。
看一下2和8都代表什么:
#2 = Class #4 // java/lang/Object#8 = Class #10 // com/flydean/JavaClassUsage沒(méi)錯(cuò),JavaClassUsage的父類是Object。
大家知道為什么java只能單繼承了嗎?因?yàn)閏lass文件里面只有一個(gè)u2的位置,放不下了!
interfaces_count和interfaces[]
接下來(lái)就是接口的數(shù)目和接口的具體信息數(shù)組了。
00 00我們沒(méi)有實(shí)現(xiàn)任何接口,所以interfaces_count=0,這時(shí)候也就沒(méi)有interfaces[]了。
fields_count和fields[]
然后是字段數(shù)目和字段具體的數(shù)組信息。
這里的字段包括類變量和實(shí)例變量。
每個(gè)字段信息也是一個(gè)結(jié)構(gòu)體:
field_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }字段的access_flag跟class的有點(diǎn)不一樣:
這里我們就不具體對(duì)比解釋了,感興趣的小伙伴可以自行體驗(yàn)。
methods_count和methods[]
接下來(lái)是方法信息。
method結(jié)構(gòu)體:
method_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }method訪問(wèn)權(quán)限標(biāo)記:
attributes_count和attributes[]
attributes被用在ClassFile, field_info, method_info和Code_attribute這些結(jié)構(gòu)體中。
先看下attributes結(jié)構(gòu)體的定義:
attribute_info {u2 attribute_name_index;u4 attribute_length;u1 info[attribute_length]; }都有哪些attributes, 這些attributes都用在什么地方呢?
其中有六個(gè)屬性對(duì)于Java虛擬機(jī)正確解釋類文件至關(guān)重要,他們是:
ConstantValue,Code,StackMapTable,BootstrapMethods,NestHost和NestMembers。
九個(gè)屬性對(duì)于Java虛擬機(jī)正確解釋類文件不是至關(guān)重要的,但是對(duì)于通過(guò)Java SE Platform的類庫(kù)正確解釋類文件是至關(guān)重要的,他們是:
Exceptions,InnerClasses,EnclosingMethod,Synthetic,Signature,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable。
其他13個(gè)屬性,不是那么重要,但是包含有關(guān)類文件的元數(shù)據(jù)。
總結(jié)
最后留給大家一個(gè)問(wèn)題,java class中常量池的大小constant_pool_count是2個(gè)字節(jié),兩個(gè)字節(jié)可以表示2的16次方個(gè)常量。很明顯已經(jīng)夠大了。
但是,萬(wàn)一我們寫(xiě)了超過(guò)2個(gè)字節(jié)大小的常量怎么辦?歡迎大家留言給我討論。
本文鏈接:http://www.flydean.com/jvm-class-file-structure/
最通俗的解讀,最深刻的干貨,最簡(jiǎn)潔的教程,眾多你不知道的小技巧等你來(lái)發(fā)現(xiàn)!
歡迎關(guān)注我的公眾號(hào):「程序那些事」,懂技術(shù),更懂你!
總結(jié)
以上是生活随笔為你收集整理的JVM详解之:java class文件的密码本的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一张PDF了解JDK10 GC调优秘籍-
- 下一篇: 看动画学算法之:排序-选择排序