详解Class类文件的结构(上)
前言
相信搞Java開發的同學都經常會接觸到Class類文件,了解了JVM虛擬機之后也會大量接觸到class字節碼,那么它到底是什么樣的文件?內部由什么構成?虛擬機又是如何去識別它的?這篇文章就來學習一下Class類文件的結構。
ps:我在面試螞蟻的時候被問到過這個問題!你沒看錯,面試也有可能會問。
一、什么是Class文件
Class文件又稱字節碼文件,一種二進制文件,它是由某種語言經過編譯而來,注意這里并不一定是Java語言,還有可能是Clojure、Groovy、JRuby、Jython、Scala等,Class文件運行在Java虛擬機上。Java虛擬機不與任何一種語言綁定,它只與Class文件這種特定的二進制文件格式所關聯。
虛擬機具有語言無關性,它不關心Class文件的來源是何種語言,它只關心Class文件中的內容。Java語言中的各種變量、關鍵字和運算符號的語義最終都是由多條字節碼命名組合而成的,因此字節碼命令所能提供的語義描述能力比Java語言本身更加強大。
二、Class文件的結構
虛擬機可以接受任何語言編譯而成的Class文件,因此也給虛擬機帶來了安全隱患,為了提供語言無關性的功能就必須做好安全防備措施,避免危險有害的類文件載入到虛擬機中,對虛擬機造成損害。所以在類加載的第二大階段就是驗證,這一步工作是虛擬機安全防護的關鍵所在,其中檢查的步驟就是對class文件按照《Java虛擬機規范》規定的內容來對其進行驗證。
1.總體結構
Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,Class文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。當遇到需要占用8位字節以上空間的數據項時,就按照高位在前的方式分割成若干個8位字節進行存儲。
Class文件格式采用類似于C語言結構體的偽結構來存儲數據,這種偽結構只有兩種數據類型:無符號數和表。
- 無符號數屬于基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節、8個字節的無符號數,無符號數可以來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
- 表是由多個無符號數或者其他表作為數據項構成的復合數據類型,所有表都習慣性的以“_info”結尾。表用于描述有層次關系的復合結構的數據,整個Class文件本質上就是一張表,它的數據項構成如下圖。
2.魔數(Magic Number)
每一個Class文件的頭4個字節成為魔數(Magic Number),它的唯一作用是確定這個文件是否是一個能被虛擬機接收的Class文件。很多文件存儲標準中都是用魔數來進行身份識別,比如gif、png、jpeg等都有魔數。使用魔數主要是來識別文件的格式,相比于通過文件后綴名識別,這種方式準確性更高,因為文件后綴名可以隨便更改,但更改二進制文件內容的卻很少。Class類文件的魔數是Oxcafebabe,cafe babe?咖啡寶貝?至于為什么是這個, 這個名字在java語言誕生之初就已經確定了,它象征著著名咖啡品牌Peet's Coffee中深受歡迎的Baristas咖啡,Java的商標logo也源于此。
3.文件版本(Version)
在魔數后面的4個字節就是Class文件的版本號,第5和第6個字節是次版本號(Minor Version),第7和第8個字節是主版本號(Major Version)。Java的版本號是從45開始的,JDK1.1之后的每個JDK大版本發布主版本號向上加1(JDK1.0~1.1使用的版本號是45.0~45.3),比如我這里是十六進制的Ox0034,也就是十進制的52,所以說明該class文件可以被JDK1.8及以上的虛擬機執行,否則低版本虛擬機執行會報java.lang.UnsupportedClassVersionError錯誤。
4.常量池(Constant Pool)
在主版本號緊接著的就是常量池的入口,它是Class文件結構中與其他項目關聯最多的數據類型,也是占用空間最大的數據之一。常量池的容量由后2個字節指定,比如這里我的是Ox001d,即十進制的29,這就表示常量池中有29項常量,而常量池的索引是從1開始的,這一點需要特殊記憶,因為程序員習慣性的計數法是從0開始的,而這里不一樣,所以我這里常量池的索引范圍是1~29。設計者將第0項常量空出來是有目的的,這樣可以滿足后面某些指向常量池的索引值的數據在特定情況下需要表達“不引用任何一個常量池項目”的含義。
通過javap -v命令反編譯出class文件之后,我們可以看到常量池的內容
常量池中主要存放兩大類常量:字面量和符號引用。比如文本字符、聲明為final的常量值就屬于字面量,而符號引用則包含下面三類常量:
- 類和接口的全限名
- 字段的名稱和描述符
- 方法的名稱和描述符
在之前的文章(詳談類加載的全過程)中有詳細講到,在加載類過程的第二大階段連接的第三個階段解析的時候,會將常量池中的符號引用替換為直接引用。相信很多人在開始了解那里的時候也是一頭霧水,作者我也是,當我了解到常量池的構成的時候才明白真正意思。Java代碼在編譯的時候,是在虛擬機加載Class文件的時候才會動態鏈接,也就是說Class文件中不會保存各個方法、字段的最終內存布局信息,因此這些字段、方法的符號引用不經過運行期轉換的話無法獲得真正的內存入口地址,也就無法直接被虛擬機使用。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中。
常量池中每一項常量都是一張表,這里我只找到了JDK1.7之前的常量池項目類型表,見下圖。
- 常量池項目類型表:
- 常量池常量項的結構總表:
比如我這里測試的class文件第一項常量,它的標志位是Ox0a,即十進制10,即表示tag為10的常量項,查表發現是CONSTANT_Methodref_info類型,和上面反編譯之后的到的第一個常量是一致的,Methodref表示類中方法的符號引用。查上面《常量池常量項的結構總表》可以看到Methodref中含有3個項目,第一個tag就是上述的Ox0a,那么第二個項目就是Ox0006,第三個項目就是Ox000f,分別指向的CONSTANT_Class_info索引項和CONSTANT_NameAndType_info索引項為6和15,那么反編譯的結果該項常量指向的應該是#6和#15,查看上面反編譯的圖應證我們的推測是對的。后面的常量項就以此類推。
這里需要特殊說明一下utf8常量項的內容,這里我以第29項常量項解釋,也就是最后一項常量項。查《常量池常量項的結構總表》可以看到utf8項有三個內容:tag、length、bytes。tag表示常量項類型,這里是Ox01,表示是CONSTANT_Utf8_info類型,緊接著的是長度length,這里是Ox0015,即十進制21,那么再緊接著的21個字節都表示該項常量項的具體內容。特別注意length表示的最大值是65535,所以Java程序中僅能接收小于等于64KB英文字符的變量和變量名,否則將無法編譯。
5.訪問標志(Access Flags)
在常量池結束后,緊接著的兩個字節代表訪問標志(Access Flags),該標志用于識別一些類或者接口層次的訪問信息,其中包括:Class是類還是接口、是否定義為public、是否定義為abstract類型、類是否被聲明為final等。
訪問標志表
標志位一共有16個,但是并不是所有的都用到,上表只列舉了其中8個,沒有使用的標志位統統置為0,access_flags只有2個字節表示,但是有這么多標志位怎么計算而來的呢?它是由標志位為true的標志位值取或運算而來,比如這里我演示的class文件是一個類并且是public的,所以對應的ACC_PUBLIC和ACC_SIPER標志應該置為true,其余標志不滿足則為false,那么access_flags的計算過程就是:Ox0001 | Ox0020 = Ox0021
篇幅原因,未完待續......
參考文獻:《深入理解Java虛擬機》
END
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的详解Class类文件的结构(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 兴业桃花信用卡审核要多久 兴业桃花信用卡
- 下一篇: 408最后计算机网络题库,2021考研计