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