淺談JVM之java class文件的密碼本
簡(jiǎn)介
機(jī)器可以讀,人為什么不能讀?只要我們掌握java class文件的密碼表,我們可以把二進(jìn)制轉(zhuǎn)成十六進(jìn)制,將十六進(jìn)制和我們的密碼表進(jìn)行對(duì)比,就可以輕松的解密了。
下面,讓我們開始這個(gè)激動(dòng)人心的過程吧。
一個(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的方法。
接下來我們使用javac來進(jìn)行編譯。
IDEA有沒有?直接打開編譯后的class文件,你會(huì)看到什么?
沒錯(cuò),是反編譯過來的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)不想都列出來,但是又一想只有對(duì)才能講述得更清楚,還是貼在下面:
public class com.flydean.JavaClassUsage
minor version: 0
major version: 58
flags: 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: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 18
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 7: 0
line 9: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/flydean/JavaClassUsage;
public void inc(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: aload_0
2: getfield #7 // Field age:I
5: iload_1
6: iadd
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
LocalVariableTable:
Start Length Slot Name Signature
0 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文件打開吧。
你準(zhǔn)備好了嗎?來吧,展示吧!
上圖左邊是16進(jìn)制的class文件代碼,右邊是對(duì)16進(jìn)制文件的適當(dāng)解析。大家可以隱約的看到一點(diǎn)點(diǎn)熟悉的內(nèi)容。
是的,沒錯(cuò),你會(huì)讀機(jī)器語言了!
class文件的密碼本
如果你要了解class文件的結(jié)構(gòu),你需要這個(gè)密碼本。
如果你想解析class文件,你需要這個(gè)密碼本。
學(xué)好這個(gè)密碼本,走遍天下都......沒啥用!
下面就是密碼本,也就是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表示的是無符號(hào)的兩個(gè)字節(jié),無符號(hào)的4個(gè)字節(jié)。
java class文件就是按照上面的格式排列下來的,按照這個(gè)格式,我們可以自己實(shí)現(xiàn)一個(gè)反編譯器(大家有興趣的話,可以自行研究)。
我們對(duì)比著上面的二進(jìn)制文件一個(gè)一個(gè)的來理解。
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 07
0xCAFEBABE就是magic word。所有的java class文件都是以這4個(gè)字節(jié)開頭的。
來一杯咖啡吧,baby!
多么有詩意的畫面。
version
這兩個(gè)version要連著講,一個(gè)是主版本號(hào),一個(gè)是次版本號(hào)。
00 00 00 3A
對(duì)比一下上面的表格,我們的主版本號(hào)是3A=58,也就是我們使用的是JDK14版本。
常量池
接下來是常量池。
首先是兩個(gè)字節(jié)的constant_pool_count。對(duì)比一下,constant_pool_count的值是:
00 17
換算成十進(jìn)制就是23。也就是說常量池的大小是23-1=22。
這里有兩點(diǎn)要注意,第一點(diǎn),常量池?cái)?shù)組的index是從1開始到constant_pool_count-1結(jié)束。
第二點(diǎn),常量池?cái)?shù)組的第0位是作為一個(gè)保留位,表示“不引用任何常量池項(xiàng)目”,為某些特殊的情況下使用。
接下來是不定長(zhǎng)度的cp_info:constant_pool[constant_pool_count-1]常量池?cái)?shù)組。
常量池?cái)?shù)組中存了些什么東西呢?
字符串常量,類和接口名字,字段名,和其他一些在class中引用的常量。
具體的constant_pool中存儲(chǔ)的常量類型有下面幾種:
每個(gè)常量都是以一個(gè)tag開頭的。用來告訴JVM,這個(gè)到底是一個(gè)什么常量。
好了,我們對(duì)比著來看一下。在constant_pool_count之后,我們?cè)偃∫徊糠?6進(jìn)制數(shù)據(jù):
上面我們講到了17是常量池的個(gè)數(shù),接下來就是常量數(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。
然后我們就這樣一路找下去,就得到了所有常量池中常量的信息。
這樣找起來,眼睛都花了,有沒有什么簡(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:訪問描述符,表示的是這個(gè)class或者接口的訪問權(quán)限。
先上密碼表:
再找一下我們16進(jìn)制的access_flag:
沒錯(cuò),就是00 21。 參照上面的表格,好像沒有21,但是別怕:
21是ACC_PUBLIC和ACC_SUPER的并集。表示它有兩個(gè)access權(quán)限。
this_class和super_class
接下來是this class和super class的名字,他們都是對(duì)常量池的引用。
00 08 00 02
this class的常量池index=8, super class的常量池index=2。
看一下2和8都代表什么:
#2 = Class #4 // java/lang/Object
#8 = Class #10 // com/flydean/JavaClassUsage
沒錯(cuò),JavaClassUsage的父類是Object。
大家知道為什么java只能單繼承了嗎?因?yàn)閏lass文件里面只有一個(gè)u2的位置,放不下了!
interfaces_count和interfaces[]
接下來就是接口的數(shù)目和接口的具體信息數(shù)組了。
00 00
我們沒有實(shí)現(xiàn)任何接口,所以interfaces_count=0,這時(shí)候也就沒有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[]
接下來是方法信息。
method結(jié)構(gòu)體:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
method訪問權(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ì)于通過Java SE Platform的類庫正確解釋類文件是至關(guān)重要的,他們是:
Exceptions,InnerClasses,EnclosingMethod,Synthetic,Signature,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable。
其他13個(gè)屬性,不是那么重要,但是包含有關(guān)類文件的元數(shù)據(jù)。
總結(jié)
最后留給大家一個(gè)問題,java class中常量池的大小constant_pool_count是2個(gè)字節(jié),兩個(gè)字節(jié)可以表示2的16次方個(gè)常量。很明顯已經(jīng)夠大了。
以上就是淺談JVM之java class文件的密碼本的詳細(xì)內(nèi)容,更多關(guān)于JVM之java class文件的密碼本的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java class文件格式之?dāng)?shù)據(jù)類型(二)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java class文件格式之方法_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java class文件格式之屬性_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java class文件格式之常量池_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java class文件格式之訪問標(biāo)志信息_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- java虛擬機(jī)原理:Class字節(jié)碼二進(jìn)制文件分析
相關(guān)文章
java中List<對(duì)象>如何根據(jù)對(duì)象的一個(gè)屬性進(jìn)行去重
這篇文章主要給大家介紹了關(guān)于java中List<對(duì)象>如何根據(jù)對(duì)象的一個(gè)屬性進(jìn)行去重的相關(guān)資料,在開發(fā)中可能會(huì)遇到很多需要去重的情況,比如Person對(duì)象有name跟age兩個(gè)屬性,需要根據(jù)age進(jìn)行去重,需要的朋友可以參考下2023-08-08Spring負(fù)載均衡LoadBalancer使用詳解
這篇文章主要介紹了Spring負(fù)載均衡LoadBalancer使用詳解,Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客戶端負(fù)載均衡器, 用來替代Ribbon,Spring官方提供了兩種客戶端都可以使用loadbalancer,需要的朋友可以參考下2023-11-11SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過程
ip2region v2.0 - 是一個(gè)離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,10微秒級(jí)別的查詢效率,提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06SpringBoot創(chuàng)建WebService方法詳解
這篇文章主要介紹了SpringBoot如何創(chuàng)建WebService,文中有詳細(xì)的實(shí)現(xiàn)步驟以及示例代碼,對(duì)學(xué)習(xí)或工作有一定的幫助,需要的朋友跟著小編一起來學(xué)習(xí)吧2023-05-05關(guān)于mybatis plus 中的查詢優(yōu)化問題
這篇文章主要介紹了關(guān)于mybatis plus 中的查詢優(yōu)化問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01LinkedBlockingQueue鏈?zhǔn)阶枞?duì)列的使用和原理解析
這篇文章主要介紹了LinkedBlockingQueue鏈?zhǔn)阶枞?duì)列的使用和原理解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Eclipse Debug模式的開啟與關(guān)閉問題簡(jiǎn)析
這篇文章主要介紹了Eclipse Debug模式的開啟與關(guān)閉問題簡(jiǎn)析,同時(shí)向大家介紹了一個(gè)簡(jiǎn)單的debug模式啟動(dòng)不起來的解決方法,希望對(duì)大家有所幫助。2017-10-10Spring?Data?JPA關(guān)系映射@OneToOne實(shí)例解析
這篇文章主要為大家介紹了Spring?Data?JPA關(guān)系映射@OneToOne實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08