java主类与源代码名称_Java高级编程基础:类文件结构解析,看穿Class代码背后的秘密...
類文件結構
在說完了JVM內部結構后,接下來我們需要說一下另外一個非常重要的基礎概念Class類結構。
我們既然知道了開發的Java源代碼會首先被編譯成字節碼文件保存,JVM的類加載器會讀取這些文件內容,然后將其轉換為Class類對象保存到JVM管理的運行數據區里。那么這個編譯后的Class類文件的結構如何呢?我們今天來簡單說一下。
其實JVM的規范里有詳細的定義和說明,不過它涉及了很多專業的名稱和術語,還有就是說的比較簡潔,理解起來還是有些難度。
類文件結構
簡單說來從存儲字節碼的.class文件中讀取的內容由10個基本組成部分構成:
簡略結構
- magic: 0xCAFEBABE
- minor_version和major_version: 包括類文件的主次版本號
- constant_pool: 當前類的常量池
- access_flags: 當前類訪問標志
- this_class: 當前類名字
- super_class: 其父類名字
- interfaces: 當前類實現的任何接口
- fields: 當前類定義的任何字段
- methods: 當前類包含的方法
- attributes: 類的任何屬性(例如源文件的名稱等)
一般情況下,該文件是由8位字節基本單位長度字節流構成,對于那些16位,32位,甚至64位的它們將分別讀取2個,4個和8個基本單位來表示。
這類多字節數據項都將是以大端順序存儲的,也就是說高位字節在前。
我們在Java開發時,常用的讀寫這類數據的接口是java.io.DataInput和java.io.DataOutput,對應的類有java.io.DataInputStream和java.io.DataOutputStream。
類文件結構
截圖中的u1,u2,u4 它們分別表示無符號的1字節,2字節和4字節類型,它們分別對應著我們Java的基礎數據類型中的byte,short,int等。我們完全可以使用java.io.DataInput接口定義的readUnsignByte,readUnsignShort, readUnsignInt等讀取其值。
另外一類就是長度在加載之前是未知的,有可變長度的部分,如常量池,方法,屬性等。這些部分的組織方式是以它們的大小或長度作為前綴的。
通過這種方式,JVM在實際加載可變長度區段之前就知道它們的大小。
在Java ClassFile中不同部分的順序是嚴格定義的,這樣JVM就知道每一步應該要加載什么,以及加載不同組件的順序。
關于魔幻數字的故事
詹姆斯·高斯林(James Gosling)曾經解釋過這個神奇數字的歷史:
他說“我們過去常去一個叫 St Michael’s Alley的地方吃午飯。據當地傳說,在黑暗的過去,Grateful Dead樂隊在成名之前經常在那里演出。這是一個非常時髦的地方,絕對是一個‘Grateful Dead Kinda Place’。Jerry死后,他們甚至建了一座佛教風格的小神龕。當我們過去常去那里,我們把那個地方稱為‘Cafe Dead’。
沿著這思路,有人注意到這是一個十六進制數。當時我重新整理了一些文件格式代碼,需要幾個神奇的數字,一個用于持久對象文件,一個用于類文件。所以我就選用CAFEDEAD作為對象文件格式,并在查詢中添加了4個字符的十六進制單詞,它們與“CAFE”匹配。我意外獲得了BABE,所以就決定使用CAFEBABE。在那個時候,它似乎并不是那么重要,或者注定要去任何地方,除了歷史的垃圾桶。
因此CAFEBABE成為類文件格式,而CAFEDEAD則是持久對象格式。但持久性對象設施消失了,隨之消失的還有CAFEDEAD的使用——它最終被RMI取代?!?/p>
類文件版本
類文件的下4個字節包含主版本號和次版本號。我們的JVM會通過這組數字驗證和標識類文件。
如果這個數量大于JVM可以裝載的數量,那么類文件將被錯誤拒絕,并提示java.lang.UnsupportedClassVersionError。
我們通??梢允褂胘avap命令行來查看任何Java類文件的類版本。
先定義一個MyClass.java文件,然后編譯成MyClass.class
public class Main {public static void main(String [] args) {int my_integer = 0xFEEDED;}}我們使用javap命令查看:
$ javap -verbose MyClass在上面的主類上執行javap,我們得到如下的符號表。
javap 顯示類文件內容
關于常量池
我們知道所有的類或者接口的定義在應用程序運行過程中都不會發生變化的,因此所有與類或接口相關的常量都將存儲在常量池中。
它們包括類名、變量名、接口名、方法名和簽名、最終變量值、字符串字面量等。
這些常量在常量池中被存儲為可變長度數組元素。
這些常量可變數組的前面是它的數組大小,因此JVM知道在加載類文件時需要多少常量。
而在每個數組元素中,第一個字節表示一個標記位,該標記指定數組中該位置的常量類型,JVM通過讀取這個字節的標記來標識常量的類型。
因此,如果這個字節標記表示一個字符串文字,那么JVM知道接下來的兩個字節表示字符串文字的長度,而條目的其余部分是字符串文字本身。
我們同樣可以使用javap命令分析任何類文件的常量池。
常量池內容
上圖中常量池總共有15個條目。
#1是方法public static void main;
#2用于整數值 0xFEEDED(16707053)。
#3和#4分別是我們類的this和super類。
剩余其它都是存儲字符串文本的符號表。
關于訪問標識
它是一個2個字節的條目,用類指示文件是定義的類還是接口,如果是一個類,它是公共的、抽象的還是final的。
這些訪問標志包括ACC_PUBLIC,ACC_FINAL,ACC_SUPER,ACC_INTERFACE,ACC_ABSTRACT,ACC_SYNTHETIC,ACC_ANNOTATION,ACC_ENUM等等。
關于this Class
this Class類是一個2個字節的條目,其內容是指向常量池中的索引。
圖片來自網絡
比如在上面的圖中,this類的值0x0007是常量池中的索引。
this 所指向的對應項Constant_pool[this_class]在常量池中有兩部分,第一部分是一個字節的標記類表示在常量池中的條目類型。
這種情況下是類或者接口類型。在上面的圖表中,這是用橙色表示的。第二個條目部分是兩個字節,同樣在常量池中有索引。
在上面的圖中,兩個字節包含值0x0004。因此它指向Constant_pool[0x0004],它的值是具有接口或類名稱的字符串文字。
關于super Class和其它集合組件
排在this Class后面那個字節就是Super Class,跟this Class類似,它是2個字節的值,也是一個指向常量池的指針,該常量池具有類的超類的條目。
接口集合
包含由該文件中定義的類或接口實現的所有接口。
接口部分開始的兩個字節是提供有關正在實現的接口總數的信息的計數,緊接著是一個數組,該數組包含了被該類實現的接口在常量池里的索引。
字段集合
字段是類或接口的實例或類級變量或屬性存儲的地方。
字段部分只包含由文件的類或接口定義的字段,而不包含從超類或超接口繼承的字段。
它的前兩個字節表示計數,是該部分中包含的所有字段數,跟在其后的是每個字段的可變長度數組。數組中每個元素都表示一個字段。
有些信息存儲在這個結構中,有些像字段名等信息存儲在常量池中。
方法集合
方法組件托管由該類顯式定義的方法,而不包括從超類繼承的任何其他方法。
同樣,其開始的前兩個字節是該類或者接口中包含的方法計數,其余的是包含每個方法結構的可變長度數組。
每個方法結構包含了有關方法的參數列表,返回值類型,方法的局部變量所需的堆棧單詞數,方法操作數堆棧所需的堆棧單詞,異常表,字節碼序列等信息。
屬性集合
屬性部分包含有關類文件的幾個屬性,比如其中一個屬性是源代碼屬性,它顯示編譯該類文件的源文件的名稱。
同樣屬性部分的前兩個字節是屬性數量的計數,然后是屬性本身。jvm將忽略它們不理解的任何屬性。
總結一下
這里我簡單的參照JVM規范說明了一下我們編寫Java源代碼后編譯成的.class文件內容,它被JVM的類加載模塊加載后,會形成上面的數據結構,了解這個結構,主要是為以后我們在Java編程中如何去理解其處理過程,代碼優化,性能調優,包括反射機制如何讀取等都有幫助。
本文是我的《Java高級編程基礎》系列的第四篇,由于Java高級編程主要是以對底層特別是Java虛擬機的理解為基礎的,雖然網上有很多專業的文章已經針對這一塊講的非常好了,我還是想從另外一個角度去重復串聯一下它們,希望能夠為想學習Java開發的小伙伴們提供另外一個理解它們的思路和學習路線圖。期待與大家一起共同探索一條Java學習的高效之道,下面是前面幾篇相關的文章,歡迎同行評論交流!
《Java高級編程基礎:深入理解虛擬機的共享堆內存和方法區》
《Java高級編程基礎:詳細解讀虛擬機底層棧幀線程模型》
《Java高級編程基礎:可以從這個思路去理解JVM和性能調優》
總結
以上是生活随笔為你收集整理的java主类与源代码名称_Java高级编程基础:类文件结构解析,看穿Class代码背后的秘密...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10的多少次方 oracle_初中数学:
- 下一篇: pandas将某一列变为索引_Panda