Class 文件中的魔數、主次版本號與常量池
1. 前言
本節內容主要是介紹 Class 文件結構中的魔數、主次版本號與常量池。本節主要知識點如下:
- Class 文件的數據類型,概念性的知識,為本節基礎知識點;
- Class 文件結構介紹,為本節次重點知識;
- 魔數的定義及所占字節空間,為本節重點內容之一;
- 次版本號與主版本號的定義及對照表,次版本號與主版本號為本節重點內容之一,版本號對照表為了解內容;
- 常量池計數器與常量池的定義及意義,為本節重點內容之一。
2. Class文件數據類型
根據 Java 虛擬機規范的規定,Class 文件格式采用一種類似于 C 語言結構體的偽結構來存儲數據,這種偽結構中只有兩種數據類型:無符號數和表。
-
無符號數:無符號數屬于基本的數據類型,以 u1、u2、u4、u8 來分別代表 1 個字節、2 個字節、4 個字節和 8 個字節;無符號數可以用來描述數字、索引引用、數量值或者按照 UTF-8 編碼構成的字符串值;
-
表:表是由多個無符號數或者其他表作為數據項構成的復合數據類型,所有表都習慣性地以“info”結尾。表用于描述有層次關系的復合結構的數據,整個 Class 文件本質上就是一張表。
Tips:無符號數和表這兩種類型的數據,初次來看非常的抽象,從概念層面來看似乎很難理解。我們無需著急, 本節所講述的魔數,次版本號,主版本號以及常量池計數器皆為無符號數類型,而常量池為表類型,講解這些結構時,我會為大家提供示意圖,使學習者從感官上看到這兩種數據類型,從而徹底理解這兩種數據類型。
3. Class 文件結構
Class 文件是一組以(8位bit的)byte 字節為基礎單位的二進制流。如下圖所示 Class 文件的字節碼示意圖:
Tips:使用普通的編輯器打開 Class 文件我們會看到亂碼,如果想要像上圖一樣觀察 Class 文件的話,需要下載專門的編輯器。WinHex 就具備這個功能,有興趣的同學可以安裝 WinHex 并使用。
4. 魔數(Magic Number)
定義:每個 Class 文件的頭 4 個字節(u4)稱為魔數(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接收的 Class 文件。所有 Class 文件,魔數均為 0xCAFEBABE。
Tips:從Class文件結構圖中,我們可以看到,Class文件的開頭確實是CAFEBABE。下載并安裝 WinHex 的學習者,如果打開任意的一個Class文件,開頭也必然是CAFEBABE。
無符號數結構示意圖:前文提到,魔數是無符號數類型的數據,對于無符號數,我們通常以 u1、u2、u4、u8 來分別代表 1 個字節、2 個字節、4 個字節和 8 個字節。我們先來看下魔數的示意圖:
Tips:上文提到過,魔數開頭為 CAFEBABE,占用 4 個字節,無符號數表示為 u4,那么其中 CA,FE,BA,BE分別占用 1 個字節,無符號數表示為 u1。至此我們了解了魔數的定義及作用,并明白了何為無符號數類型,掌握了魔數的無符號數表示為 u4。
5. 次版本號與主版本號
學前疑問:學習者可能會有疑問,為什么標題說的是次版本號與主版本號,次版本號在前,而主版本號在后呢?先次后主,是不是讀起來感覺有點別扭呢?
疑問解答:特別強調下,對于Class 文件結構,第一部分為 u4 的魔數,魔數后邊緊跟的就是 u2 的次版本號,次版本號后邊才是 u2 的主版本號,此處需要特別注意,從結構上來說,次版本號在前,主版本號在后。
定義:次版本號與主版本號共同標識了我們所使用的的 JDK 版本,如 JDK 1.8.0 版本的次版本號為 u2 大小,用字節碼表示為 00 00,主版本號也是 u2 大小,用字節碼表示為 00 34。
- 次版本號:JDK 版本的小版本號;
- 主版本號:JDK 版本的大版本號。
Tips:如果 Class 文件的開頭 8 個字節分別為 CA FE BA BE 00 00 00 34,那么我們可以確定,這是一個 JVM 可識別的 Class 文件,且使用的 JDK 1.8.0的版本進行的編譯,因為前4個字節魔數為 CA FE BA BE 符合標準,后4 個字節 00 00 00 34 為 JDK 1.8.0的版本。
無符號數結構示意圖:前文提到,次版本號與主版本號也是無符號數類型的數據。我們接下來看下魔數的示意圖:
Tips:至此我們了解了先次后主,了解了次版本號與主版本號分別占用 2 個字節,無符號數表示為 u2。同時我們從整體上了解了,魔數后邊為次版本號,次版本號后邊為主版本號。主版本號后邊緊跟的是什么?不要著急,我們繼續學習。
版本號對照表:開篇的前言部分已經說過,對照表部分為了解內容,這里簡單舉出幾個版本的對照表,了解一下即可。
JDK 版本 | 16進制字節碼 |
---|---|
1.8.0 | 00 00 00 34 |
1.7.0 | 00 00 00 33 |
1.6.0 | 00 00 00 32 |
1.5.0 | 00 00 00 31 |
6. 常量池計數器與常量池
Tips:前文提出過,主版本號后邊緊跟的是什么,現在我們揭開答案,主版本號后邊緊跟的是常量池計數器,常量池計數器后邊緊跟的是常量池。那么常量池后邊緊跟的是什么?此處又提出問題,我們后續講解會有解答。
定義:我們先來看下兩者的定義。
- 常量池計數器:記錄常量池中的常量的數量。由于常量池中的常數的數量是不固定的,所以在常量池的入口放置了一個 u2 類型的數據,來代表常量池容器記數值(constant_pool_count)。常量池計數器也是無符號數類型數據。
- 常量池:Class 文件中的資源倉庫,它是 Class 文件結構中與其他項目關聯最多的數據類型,也是占用Class文件空間最多的數據項目之一,同時它還是 Class 文件中第一個出現的表類型數據項目。
Tips:相信學習者對常量池的興趣會比較大,為什么這么說呢?從常量池定義中,我們看到了一句話:“它還是Class文件中第一個出現的表類型數據項目”。表類型數據,終于等到了 Class 文件的表類型數據結構,我們本節會為學習者提供表類型的機構示意圖。
常量池計數器無符號數結構示意圖:我們還是要按照 Class 文件的結構順序一步一步來說,先要搞明白常量池計數器,然后再去學習表類型的常量池。
常量池計數器,我們對于這種無符號數結構其實已經非常的了解了,所以此處我們點到即止,了解常量池計數器的定義及作用,了解了常量池計數器占用 u2 大小即可。
常量池表結構示意圖:我們終于接觸到了 Class 文件中的表結構,那么我們先睹為快,然后再講解常量池的重要知識點。
常量池中存儲的數據:常量池中主要存放著兩種常量,字面量(Literal)和符號引用(Synbolic References)。
- 字面量包括:文本字符、聲明為 final 的常量值、基礎數據類型的值等;
- 符號引用包括:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。
cp_info類型:cp_info 又可細分為 14 種結構類型。下表中第二列所說的標志,是指每一種數據類型的標記值,此處做簡單了解即可。
7. 小結
本節講解了 Class 文件結構開頭的 3 種結構,魔數,次版本號與主版本號,常量池計數器與常量池。我們了解了它們的定義及意義,也了解了什么是無符號類型數據,什么是表類型數據,我們需要進行重點掌握。
本節我們也拋出了問題,常量池后邊緊跟的結構是什么?我們會在下篇課程中進行講解。本節所了解到的無符號數與表類型數據,有助于我們后續對其他 Class 文件結構的學習。