https://www.toutiao.com/article/7243069613830193668/?log_from=c3ea2be8487f1_1692607127801 1、前端編譯器和后端編譯器 Java源代碼的編譯結(jié)果是字節(jié)碼,那么肯定需要有一種編譯器能夠?qū)ava源碼編譯為字節(jié)碼,承擔這個重要責任的就是配置在path環(huán)境變量中的javac編譯器。javac是一種能夠?qū)ava源碼編譯為字節(jié)碼的前端編譯器。HotSpot并沒有強制要求前端編譯器只能使用javac來編譯字節(jié)碼,其實只要編譯結(jié)果符合JVM規(guī)范都可以被JVM所識別即可。在Java的前端編譯器領(lǐng)域,除了javac之外,還有一種被大家經(jīng)常用到的前端編譯器,那就是內(nèi)置在Eclipse中的ECJ(EclipseCompiler for Java)編譯器。和Javac的全量式編譯不同,ECJ是一種增量式編譯器。 在Eclipse中,當開發(fā)人員編寫完代碼后,使用“Ctr1+S”快捷鍵時,ECJ編譯器所采取的編譯方案是把未編譯部分的源碼逐行進行編譯,而非每次都全量編譯。因此EC3的編譯效率會比javac更加迅速和高效,當然編譯質(zhì)量和javac相比大致還是一樣的。ECJ不僅是Eclipse的默認內(nèi)置前端編譯器,在Tomcat中同樣也是使用ECJ編譯器來編譯jsp文件。由于ECJ編譯器是采用GPLv2的開源協(xié)議進行源代碼公開,所以,大家可以登錄ecliple官網(wǎng)下載ECJ編譯器的源碼進行二次開發(fā)。默認情況下,IntelliJ IDEA使用 javac編譯器。(還可以自己設(shè)置為AspectJ編譯器ajc)。前端編譯器并不會直接涉及編譯優(yōu)化等方面的技術(shù),而是將這些具體優(yōu)化細節(jié)移交給HotSpot的JIT編譯器負責。 2、學字節(jié)碼意義 通過字節(jié)碼分析,可以看到操作碼代碼執(zhí)行的細節(jié),對疑難問題排查定位更高效。 3、字節(jié)碼文件3.1、字節(jié)碼文件源代碼經(jīng)過編譯器編譯之后便會生成一個字節(jié)碼文件,字節(jié)碼是一種二進制的類文件,它的內(nèi)容是JVM的指令,而不像C、C++經(jīng)由編譯器直接生成機器碼。I 3.2、字節(jié)碼指令Java虛擬機的指令由一個字節(jié)長度的、代表著某種特定操作含義的操作碼(opcode)以及跟隨其后的零至多個代表此操作所需參數(shù)的操作數(shù)( operand)所構(gòu)成。為了簡化,虛擬機中許多指令并不包含操作數(shù),只有一個操作碼。 3.3、查看二進制字節(jié)碼3.3.1、使用Notepad++安裝HEX-Editor插件后打開3.3.2、使用Binary Viewer軟件打開4、Class文件結(jié)構(gòu)4.1、字節(jié)碼文件結(jié)構(gòu)官方文檔: 4.2、 class文件格式Class 的結(jié)構(gòu)不像XNL等描述語言,由于它沒有任何分隔符號。所以在其中的數(shù)據(jù)項,無論是字節(jié)順序還是數(shù)量,都是被嚴格限定的,哪個字節(jié)代表什么含義,長度是多少,先后順序如何,都不允許改變。Class 文件格式采用一種類似于C語言結(jié)構(gòu)體的方式進行數(shù)據(jù)存儲,這種結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
5、魔數(shù)
6、文件版本號緊接著魔數(shù)的 4 個字節(jié)存儲的是 Class 文件的版本號。同樣也是 4 個字節(jié)。第 5 個和第 6 個字節(jié)所代表的含義就是編譯的副版本號 minor_version,而第 7 個和第 8 個字節(jié)就是編譯的主版本號 major_version。它們共同構(gòu)成了 class 文件的格式版本號。譬如某個 Class 文件的主版本號為 M,副版本號為 m,那么這個 Class 文件的格式版本號就確定為 M.m。版本號和 Java 編譯器的對應關(guān)系如下表: Java 的版本號是從 45 開始的,JDK1.1 之后的每個 JDK 大版本發(fā)布主版本號向上加 1。不同版本的 Java 編譯器編譯的 Class 文件對應的版本是不一樣的。目前,高版本的 Java 虛擬機可以執(zhí)行由低版本編譯器生成的 Class 文件,但是低版本的 Java 虛擬機不能執(zhí)行由高版本編譯器生成的 Class 文件。否則 JVM 會拋出 7、常量池集合常量池是 Class 文件中內(nèi)容最為豐富的區(qū)域之一。常量池對于 Class 文件中的字段和方法解析也有著至關(guān)重要的作用。隨著 Java 虛擬機的不斷發(fā)展,常量池的內(nèi)容也日漸豐富。可以說,常量池是整個 Class 文件的基石。在版本號之后,緊跟著的是常量池的數(shù)量,以及若干個常量池表項。常量池中常量的數(shù)量是不固定的,所以在常量池的入口需要放置一項 u2 類型的無符號數(shù),代表常量池容量計數(shù)值(constant_pool_count)。與 Java 中語言習慣不一樣的是,這個容量計數(shù)是從 1 而不是 0 開始的。因為它把第 0 項常量空出來了。這是為了滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達“不引用任何一個常量池項目”的含義,這種情況可用索引值 0 來表示。 Class 文件使用了一個前置的容量計數(shù)器(constant_pool_count)加若干個連續(xù)的數(shù)據(jù)項(constant_pool)的形式來描述常量池內(nèi)容。我們把這一系列連續(xù)常量池數(shù)據(jù)稱為常量池集合。常量池表項中,用于存放編譯時期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放 7.1、常量池計數(shù)器由于常量池的數(shù)量不固定,時長時短,所以需要放置兩個字節(jié)來表示常量池容量計數(shù)值。常量池容量計數(shù)值(u2 類型):從 1 開始,表示常量池中有多少項常量。即 constant_pool_count=1 表示常量池中有 0 個常量項。 7.2、常量池表constant_pool 是一種表結(jié)構(gòu),以 1 ~ constant_pool_count - 1 為索引。表明了后面有多少個常量項。常量池主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。它包含了 class 文件結(jié)構(gòu)及其子結(jié)構(gòu)中引用的所有字符串常量、類或接口名、字段名和其他常量。常量池中的每一項都具備相同的特征。第 1 個字節(jié)作為類型標記,用于確定該項的格式,這個字節(jié)稱為 tag byte(標記字節(jié)、標簽字節(jié))。 字面量和符號引用 常量池主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。如下表:
全限定名
簡單名稱 簡單名稱是指沒有類型和參數(shù)修飾的方法或者字段名稱。 描述符 描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的 void 類型都用一個大寫字符來表示,而對象類型則用字符 L 加對象的全限定名來表示,詳見下表:
用描述符來描述方法時,按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴格順序放在一組小括號“()”之內(nèi)。如方法 java.lang.String tostring()的描述符為()Ljava/lang/String; ,方法 int abc(int[]x, int y)的描述符為([II)I。 虛擬機在加載 Class 文件時才會進行動態(tài)鏈接,也就是說,Class 文件中不會保存各個方法和字段的最終內(nèi)存布局信息。因此,這些字段和方法的符號引用不經(jīng)過轉(zhuǎn)換是無法直接被虛擬機使用的。當虛擬機運行時,需要從常量池中獲得對應的符號引用,再在類加載過程中的解析階段將其替換為直接引用,并翻譯到具體的內(nèi)存地址中。
常量池中每一項常量都是一個表,J0K1.7 之后共有 14 種不同的表結(jié)構(gòu)數(shù)據(jù)。根據(jù)上圖每個類型的描述我們也可以知道每個類型是用來描述常量池中哪些內(nèi)容(主要是字面量、符號引用)的。比如: CONSTANT_Integer_info 是用來描述常量池中字面量信息的,而且只是整型字面量信息。標志為 15、16、18 的常量項類型是用來支持動態(tài)語言調(diào)用的(jdk1.7 時才加入的)。 常量池總結(jié) : 這 14 種表(或者常量項結(jié)構(gòu))的共同點是:表開始的第一位是一個 u1 類型的標志位(tag),代表當前這個常量項使用的是哪種表結(jié)構(gòu),即哪種常量類型。 在常量池列表中,CONSTANT_Utf8_info 常量項是一種使用改進過的 UTF-8 編碼格式來存儲諸如文字字符串、類或者接口的全限定名、字段或者方法的簡單名稱以及描述符等常量字符串信息。 這 14 種常量項結(jié)構(gòu)還有一個特點是,其中 13 個常量項占用的字節(jié)固定,只有 CONSTANT_Utf8_info 占用字節(jié)不固定,其大小由 length 決定。為什么呢?因為從常量池存放的內(nèi)容可知,其存放的是字面量和符號引用,最終這些內(nèi)容都會是一個字符串,這些字符串的大小是在編寫程序時才確定,比如你定義一個類,類名可以取長取短,所以在沒編譯前,大小不固定,編譯后,通過 utf-8 編碼,就可以知道其長度。 常量池:可以理解為 Class 文件之中的資源倉庫,它是 Class 文件結(jié)構(gòu)中與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型(后面的很多數(shù)據(jù)類型都會指向此處),也是占用 Class 文件空間最大的數(shù)據(jù)項目之一。 Java 代碼在進行 Javac 編譯的時候,并不像 C 和 C++那樣有“連接”這一步驟,而是在虛擬機加載 C1ass 文件的時候進行動態(tài)鏈接。也就是說,在 Class 文件中不會保存各個方法、字段的最終內(nèi)存布局信息,因此這些字段、方法的符號引用不經(jīng)過運行期轉(zhuǎn)換的話無法得到真正的內(nèi)存入口地址,也就無法直接被虛擬機使用。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創(chuàng)建時或運行時解析、翻譯到具體的內(nèi)存地址之中。 8、訪問標志在常量池后,緊跟著訪問標記。該標記使用兩個字節(jié)表示,用于識別一些類或者接口層次的訪問信息,包括:這個 Class 是類還是接口;是否定義為 public 類型;是否定義為 abstract 類型;如果是類的話,是否被聲明為 final 等。各種訪問標記如下所示:
類的訪問權(quán)限通常為 ACC_開頭的常量。每一種類型的表示都是通過設(shè)置訪問標記的 32 位中的特定位來實現(xiàn)的。比如,若是 public final 的類,則該標記為 ACC_PUBLIC | ACC_FINAL。使用 ACC_SUPER 可以讓類更準確地定位到父類的方法 super.method(),現(xiàn)代編譯器都會設(shè)置并且使用這個標記。
9、類索引、父類索引、接口索引在訪問標記后,會指定該類的類別、父類類別以及實現(xiàn)的接口,格式如下:
類索引、父類索引、接口索引這三項數(shù)據(jù)來確定這個類的繼承關(guān)系:
9.1、this_class(類索引)2 字節(jié)無符號整數(shù),指向常量池的索引。它提供了類的全限定名,如 9.2、super_class(父類索引)2 字節(jié)無符號整數(shù),指向常量池的索引。它提供了當前類的父類的全限定名。如果我們沒有繼承任何類,其默認繼承的是 java/lang/object 類。同時,由于 Java 不支持多繼承,所以其父類只有一個。super_class 指向的父類不能是 final。 9.3、interfaces指向常量池索引集合,它提供了一個符號引用到所有已實現(xiàn)的接口 由于一個類可以實現(xiàn)多個接口,因此需要以數(shù)組形式保存多個接口的索引,表示接口的每個索引也是一個指向常量池的 CONSTANT_Class(當然這里就必須是接口,而不是類)。
后續(xù)介紹字段表集合、方法表集合、屬性表集合。 |
|