vs当前文件的函数索引_VM实战(六) - 通过案例深入学习class文件结构原理
0 更多干貨關注 JavaEdge 公眾號
1 什么是JVM的“無關性”?
Java具有平臺無關性,也就是任何操作系統都能運行Java代碼.之所以能實現這一點,是因為Java運行在虛擬機之上,不同的操作系統都擁有各自的Java虛擬機,因此Java能實現"一次編寫,處處運行".
而JVM不僅具有平臺無關性,還具有語言無關性.
- 平臺無關性是指不同操作系統都有各自的JVM
- 語言無關性是指Java虛擬機能運行除Java以外的代碼!
這聽起來非常驚人,但JVM對能運行的語言是有嚴格要求的.首先來了解下Java代碼的運行過程.
Java源代碼首先需要使用Javac編譯器編譯成class文件,然后啟動JVM執行class文件,從而程序開始運行.
也就是JVM只認識class文件,它并不管何種語言生成了class文件,只要class文件符合JVM的規范就能運行.
因此目前已經有Scala、JRuby、Jython等語言能夠在JVM上運行.它們有各自的語法規則,不過它們的編譯器都能將各自的源碼編譯成符合JVM規范的class文件,從而能夠借助JVM運行它們.
2 縱觀Class文件結構
class文件包含Java程序執行的字節碼
數據嚴格按照格式緊湊排列在class文件中的二進制流,中間無任何分隔符
文件開頭有一個0xcafebabe(16進制)特殊的一個標志
- 下圖展示為16進制
class文件是一組以8位字節為基礎單位的二進制流,它的內容具有嚴格的規范,文件中沒有任何分隔符,全是連續的0/1.
class文件中的所有內容被分為兩種類型:無符號數 和 表。
- 無符號數
- 基本的數據類型,以u1、u2、u4、u8,分別代表1字節、2字節、4字節、8字節的無符號數.
- 表
- class文件中所有數據(即無符號數)要么單獨存在,要么由多個無符號數組成二維表.即class文件中的數據要么是單個值,要么是二維表.
實踐
- Demo1.java
- Demo1.txt
- 版本號規則: JDK5,6,7,8
- 分別對應49,50,51,522.1 魔數(Magic Number)class文件的頭4個字節稱為魔數,唯一作用是確定這個文件是否為一個能被JVM接受的Class文件.
- 作用就相當于文件后綴名,只不過后綴名容易被修改,不安全.
- 是用16進制表示的"CAFEBABE".2.2 版本信息緊接著魔數的4個字節是版本號.它表示本class中使用的是哪個版本的JDK.
- 在高版本的JVM上能夠運行低版本的class文件,但在低版本的JVM上無法運行高版本的class文件.
2.3 常量池
2.3.1 什么是常量池?
緊接著版本號之后的就是常量池.
常量池中存放兩種類型的常量:
- 字面量 (Literal)
- 接近Java語言的常量概念,如:字符串文本、final常量值等.
- 符號引用 (Symbolic Reference)
- 屬于編譯原理方面,包括下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
2.3.2 常量池的特點
- 長度不固定
- 常量池的大小不固定,因此常量池開頭放置一個u2類型的無符號數,代表當前常量池的容量.
- 該值從1開始,若為5表示池中有4項常量,索引值1~5
- 常量由二維表表示
- 開頭有個常量池容量計數值,接下來就全是一個個常量了,只不過常量都是由一張張二維表構成,除了記錄常量的值以外,還記錄當前常量的相關信息
- class文件的資源倉庫
- 與本class中其它部分關聯最多的數據類型
- 占用Class文件空間最大的部分之一 ,也是第一個出現的表類型項目
2.3.3 常量池中常量的類型
根據常量的數據類型不同,被細分為14種常量類型,都有各自的二維表示結構
每種常量類型的頭1個字節都是tag,表示當前常量屬于14種類型中的哪一個.
以CONSTANT_Class_info常量為例,它的二維表示結構如下:
CONSTANT_Class_info表
類型名稱數量u1tag1u2name_index1
- tag 表示當前常量的類型(當前常量為CONSTANT_Class_info,因此tag的值應為7,表一個類或接口的全限定名);
- name_index 表示這個類或接口全限定名的位置.它的值表示指向常量池的第幾個常量.它會指向一個CONSTANT_Utf8_info類型的常量
類型名稱數量u1tag1u2length1u1byteslength
CONSTANT_Utf8_info表字符串常量
- tag 表當前常量的類型,這里是1
- length 表該字符串的長度
- bytes為這個字符串的內容(采用縮略的UTF8編碼)
Java中定義的類、變量名字必須小于64K
類、接口、變量等名字都屬于符號引用,它們都存儲在常量池中
而不管哪種符號引用,它們的名字都由CONSTANT_Utf8_info類型的常量表示,這種類型的常量使用u2存儲字符串的長度
由于2字節最多能表示65535個數,因此這些名字的最大長度最多只能是64K
UTF-8編碼 VS 縮略UTF-8編碼
前者每個字符使用3個字節表示,而后者把128個ASCII碼用1字節表示,某些字符用2字節表示,某些字符用3字節表示。
- Demo1.txt中的常量池部分
- 類信息包含的靜態常量,編譯之后就能確認
2.4 訪問控制在常量池結束之后是2字節的訪問控制
表示這個class文件是類/接口、是否被public/abstract/final修飾等.
由于這些標志都由是/否表示,因此可以用0/1表示.
訪問標志為2字節,可以表示16位標志,但JVM目前只定義了8種,未定義的直接寫0.
- Demo1.txt中的構造方法
Demo1這個示例中,我們并沒有寫構造函數。
由此可見,沒有定義構造函數時,會有隱式的無參構造函數
2.5 類索引、父類索引、接口索引集合
表示當前class文件所表示類的名字、父類名字、接口們的名字.
它們按照順序依次排列,類索引和父類索引各自使用一個u2類型的無符號常量,這個常量指向CONSTANT_Class_info類型的常量,該常量的bytes字段記錄了本類、父類的全限定名.
由于一個類的接口可能有好多個,因此需要用一個集合來表示接口索引,它在類索引和父類索引之后.這個集合頭兩個字節表示接口索引集合的長度,接下來就是接口的名字索引.
2.6 字段表的集合
2.6.1 什么是字段表集合?
用于存儲本類所涉及到的成員變量,包括實例變量和類變量,但不包括方法中的局部變量.
每一個字段表只表示一個成員變量,本類中所有的成員變量構成了字段表集合.
2.6.2 字段表結構的定義
- access_flags
- 字段的訪問標志。在Java中,每個成員變量都有一系列的修飾符,和上述class文件的訪問標志的作用一樣,只不過成員變量的訪問標志與類的訪問標志稍有區別。
- name_index
- 本字段名字的索引。指向一個CONSTANT_Class_info類型的常量,這里面存儲了本字段的名字等信息。
- descriptor_index
- 描述符。用于描述本字段在Java中的數據類型等信息(下面詳細介紹)
- attributes_count
- 屬性表集合的長度。
- attributes
- 屬性表集合。到descriptor_index為止是字段表的固定信息,光有上述信息可能無法完整地描述一個字段,因此用屬性表集合來存放額外的信息,比如一個字段的值。(下面會詳細介紹)2.6.3 什么是描述符?成員變量(包括靜態成員變量和實例變量) 和 方法都有各自的描述符。
- 對于字段而言,描述符用于描述字段的數據類型;
- 對于方法而言,描述符用于描述字段的數據類型、參數列表、返回值。
在描述符中,基本數據類型用大寫字母表示,對象類型用“L對象類型的全限定名”表示,數組用“[數組類型的全限定名”表示。
描述方法時,將參數根據上述規則放在()中,()右側按照上述方法放置返回值。而且,參數之間無需任何符號。
2.6.4 字段表集合的注意點
- 一個class文件的字段表集合中不能出現從父類/接口繼承而來字段;
- 一個class文件的字段表集合中可能會出現程序猿沒有定義的字段
- 如編譯器會自動地在內部類的class文件的字段表集合中添加外部類對象的成員變量,供內部類訪問外部類。
- Java中只要兩個字段名字相同就無法通過編譯。但在JVM規范中,允許兩個字段的名字相同但描述符不同的情況,并且認為它們是兩個不同的字段。
- Demo1.txt中的程序入口main方法
2.7 方法表的集合
在class文件中,所有的方法以二維表的形式存儲,每張表來表示一個函數,一個類中的所有方法構成方法表的集合。
方法表的結構和字段表的結構一致,只不過訪問標志和屬性表集合的可選項有所不同。
方法表的屬性表集合中有一張Code屬性表,用于存儲當前方法經編譯器編譯過后的字節碼指令。
方法表集合的注意點
- 如果本class沒有重寫父類的方法,那么本class文件的方法表集合中是不會出現父類/父接口的方法表;
- 本class的方法表集合可能出現程序猿沒有定義的方法
- 編譯器在編譯時會在class文件的方法表集合中加入類構造器和實例構造器。
- 重載一個方法需要有相同的簡單名稱和不同的特征簽名。JVM的特征簽名和Java的特征簽名有所不同:
- Java特征簽名:方法參數在常量池中的字段符號引用的集合
- JVM特征簽名:方法參數+返回值
2.8 屬性表的集合
- 1
- 2
程序完整運行分析
總結
我們將JVM運行的核心邏輯進行了詳細剖析。
JVM運行原理中更底層實現,針對不同的操作系統或者處理器,會有不同的實現。
這也是JAVA能夠實現“一處編寫,處處運行”的原因。
開發人員理解到這個層次,就足夠掌握高深的多線程
參考
- 《碼出高效》
總結
以上是生活随笔為你收集整理的vs当前文件的函数索引_VM实战(六) - 通过案例深入学习class文件结构原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js 连接mysql_JS连接数据库
- 下一篇: yum mysql 启动失败_Linux