一、什麼是位元組碼
Java位元組碼是Java虛拟機所使用的指令集,是八位位元組的二進制流,資料項按順序存儲在class檔案中,相鄰的項之間沒有任何間隔,這樣可以使得class檔案緊湊。任何一個Class檔案都對應着唯一的一個類或接口的定義資訊,但是反過來說,類或接口并不一定都得定義在檔案(譬如類或接口也可以動态生成,直接送入類加載器中),也就是有一些class可以不需要以磁盤檔案的形式存在。
簡單的來說位元組碼檔案即.java檔案通過javac指令生成的.class檔案。
jvm運作的是.class檔案 而java kotlin等語言都可以通過編譯器編譯成.class檔案
jvm會把編譯的.class檔案通過加載 到類加載子系統中 完成連接配接、初始化的步驟 成為class對象之後運作
二、Class類檔案的結構
序号
名稱
意思
類型
數量
1
magic
魔數
U4
2
minor_version
次闆号
U2
3
major_version
主版本号
4
constant_pool_count
常量池大小
5
constant_pool
常量池
-
costant_pool_count - 1
6
access_flags
類的通路控制權限
7
this_class
類名
8
super_class
父類名
9
interfaces_count
接口數量
10
interfaces[]
實作的接口
11
fields_count
成員屬性數量
12
field_info[]
成員屬性值
13
methods_count
方法數量
14
method_info[]
方法值
method_count
15
attributes_count
類屬性數量
16
attribute_info[]
類屬性值
根據《Java虛拟機規範》的規定,Class檔案格式采用一種類似于C語言結構體的僞結構來存儲資料,這種僞結構中隻有兩種資料類型:“無符号數”和“表”
無符号數屬于基本資料類型,以u1、u2、u4、u8來分别代表1個位元組、2個位元組、4個位元組、8個位元組的無符号數,無符号數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字元串值
表:由多個無符号數或者其他表作為資料項構成的複合資料類型,以命名_info結尾。
接下來我們就來一一了解Class檔案各組成部分,為了更直覺的了解我們打開一個Class檔案作為參照,因為class檔案是16進制存儲的,我們需要用一些工具打開,不然直接打開是亂碼,我使用的是UltraEdit的軟體。
接下來的例子都是圍繞這個類的.class檔案展開的
每個Class檔案的頭4個位元組被稱為魔數(Magic Number),固定值為0xCAFEBABE。魔數的作用是表示檔案的類型,比如PNG圖檔檔案、MP4可播放檔案、PDF等檔案基本都有自己的特殊的魔數,第三方解析器例如浏覽器就可以通過魔數字元識别出檔案的類型然後進行對應的邏輯解析處理。
我們這裡隻要記住class檔案的魔數數字就是cafe babe。class的魔數的作用是判斷該檔案是不是一個合格class檔案。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yYxcDNlNDNwMWZjdjNhJWM5EzMmJzM1EWOmhjMmBzY38CXzEzLcdDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL5M3Lc9CX6MHc0RHaiojIsJye.png)
占2個位元組 次版本号一般全部固定為零,隻有在Java2出現前被短暫使用過。但在JDK12時期,由于JDK提供的功能集已經非常龐大,有一些複雜的新特性需要以“公測”的形式放出,是以設計者重新啟用了副版本号,将它用于辨別“技術預覽版”功能特性的支援。
主版本号作用是區分jdk的版本。1.0的主版本号是44,8.0的版本号是52。高版本的JDK能向下相容以前版本的Class檔案,但不能運作以後版本的Class檔案。
比如你是在jdk8上編譯的,你到運作環境是jdk7的上去運作,就不會讓你運作 就是用過版本号來判斷的。
注意:主版本号是34是16進制 得轉化成10進制 34轉化成10進制就是52 說明我們用的是JDK8
由于常量池中常量的數量是不固定的,是以在常量池的入口需要放置一項u2類型的資料,代表常量池容量計數值。需要注意的是這個容量計數是從1而不是0開始。占2個位元組。
注意:常量池大小是2B 轉10進制為43 說明有42項常量(容量計數是從1而不是0開始)
我們的常量池可以看作我們的java class類的一個資源倉庫(比如Java類定的 方法和變量資訊),我們後面的方法、類的資訊的描述資訊都是通過索引去常量池中擷取。常量池是表類型資料項目。
常量池主要存放兩種常量: 字面量和符号引用,字面量比較接近于Java語言層面的常量概念,而符号引用則屬于編譯原理方面的概念。
1、字面量包含:文本字元串、final常量值、基本資料類型等
2、符号引用包含:類與接口的的全類名、字段和名稱的描述符、方法名稱和描述符等
常量池有三種
1、class中的常量池 靜态的(我們這裡分析的就是這個常量池 .class檔案裡的符号引用)
2、運作時常量池 動态的(加載或運作時把符号引用轉化為直接引用 靜态連結[加載階段的解析過程]和動态連接配接[棧幀方法調用過程中])
3、字元串常量池(jdk1.6字元串常量池是包含在運作常量池中的 jdk1.7字元串常量池從永久代裡的運作時常量池分離到堆裡)
常量池中每一項常量都是一個表,表結構起始的第一位是個u1類型的标志位(tag取值見表中标志列),代表着目前常量屬于哪種常量類型。
讀取第一個标志位為0A 轉10進制為10 到常量池的項目類型表中查詢到代表于CONSTANT_Methodref_info類型
再到結構總表中查詢出它的結構
具體分析第一個常量:
如何驗證我們分析的類型是否正确 可以通過idea插件jclasslib或者指令行模式去驗證:
就這樣依次向下分析 每分析完一個對照分析看是否正确
占兩個位元組 讀取00 21 代表public
00 06 代表指向常量池 #6的位址
00 07 代表指向常量池 #7的位址
00 00 如果為0 實作接口interface[]這片區域在位元組碼檔案中不會出現
因為接口數量為0 沒有這片區域 跳過
00 02 說明成員屬性有2個
我們開始分析兩個成員屬性值
驗證我們讀取的結果:
00 03 說明方法有3個(注意要把構造方法算進去)
我們開始分析三個方法值
最後也是可以通過指令行列印出來的資訊來驗證我們讀取的結果
讀取兩個 00 01
類屬性值的存儲結構:
按照資料結構讀取
到此剛好讀取完成