php编译成二进制文件_JVM字节码文件概述
字節(jié)碼文件概述
字節(jié)碼文件的跨平臺性
Java語言:跨平臺的語言
當(dāng)Java源代碼成功編譯字節(jié)碼后,如果想在不同的平臺上面運(yùn)行,則無需再次編譯
這個優(yōu)勢目前來說已經(jīng)不再吸引人,因?yàn)镻ython、PHP、Ruby、Lisp等有強(qiáng)大的解釋器
跨平臺已經(jīng)快成為一門語言的必選特性
Java虛擬機(jī):跨語言的平臺
Java虛擬機(jī)不和包括Java在內(nèi)的任何語言綁定,它只與Class文件這種特定的二進(jìn)制文件所關(guān)聯(lián),無論使用何種語言進(jìn)行軟件開發(fā),只要能將源文件編譯為正確的Class文件,那么這種語言就可以這Java虛擬機(jī)上執(zhí)行。
JVM的平臺無關(guān)性想要讓一個Java程序正確的運(yùn)行在JVM中,Java源碼就必須要被編譯為符合JVM規(guī)范的字節(jié)碼。
前端編譯器的主要任務(wù)就是負(fù)責(zé)將符合Java語法規(guī)范的Java代碼轉(zhuǎn)換為符合JVM規(guī)范的字節(jié)碼文件
javac是一種能夠?qū)ava源碼編譯為字節(jié)碼的前端編譯器
javac編譯器這將Java源碼編譯為一個有效的字節(jié)碼文件過程中經(jīng)歷了4個步驟,分別是詞法解析、語法解析、語義解析以及生成字節(jié)碼
Java的編譯
理解執(zhí)行引擎前端編譯
Java源代碼的編譯結(jié)果是字節(jié)碼,那么肯定要有一種能夠?qū)ava源代碼編譯為字節(jié)碼,承擔(dān)這個責(zé)任的就是一配置在path環(huán)境變量中的javac編譯器。javac是一種能夠?qū)ava源碼編譯為字節(jié)碼的前端編譯器。
優(yōu)點(diǎn):
許多Java語法新特性(泛型、內(nèi)部類等),是靠前端編譯器實(shí)現(xiàn)的,而不是依賴虛擬機(jī)。
編譯成的Class文件可以直接給JVM解釋器解釋執(zhí)行,省去編譯時間,加快啟動速度。
缺點(diǎn):
對代碼運(yùn)行效率幾乎沒有任何優(yōu)化措施。
解釋執(zhí)行效率較低,所以需要結(jié)合下面的JIT編譯。
后端編譯/JIT編譯
通過Java虛擬機(jī)(JVM)內(nèi)置的即時編譯器(Just In Time Compiler,JIT編譯器);在運(yùn)行時把Class文件字節(jié)碼編譯成本地機(jī)器碼的過程。
優(yōu)點(diǎn):
通過在運(yùn)行時收集監(jiān)控信息,把"熱點(diǎn)代碼"(Hot Spot Code)編譯成與本地平臺相關(guān)的機(jī)器碼,并進(jìn)行各種層次的優(yōu)化。
可以大大提高執(zhí)行效率。
缺點(diǎn):
收集監(jiān)控信息影響程序運(yùn)行。
編譯過程占用程序運(yùn)行時間。
編譯機(jī)器碼占用內(nèi)存。
靜態(tài)提前編譯(AOT)
程序運(yùn)行前,直接把Java源碼文件編譯成本地機(jī)器碼的過程。
優(yōu)點(diǎn):
編譯不占用運(yùn)行時間,可以做一些較耗時的優(yōu)化,并可加快程序啟動。
把編譯的本地機(jī)器碼保存磁盤,不占用內(nèi)存,并可多次使用。
缺點(diǎn):
因?yàn)镴ava語言的動態(tài)性(如反射)帶來了額外的復(fù)雜性,影響了靜態(tài)編譯代碼的質(zhì)量,一般靜態(tài)編譯不如JIT編譯的質(zhì)量,這種方式用得比較少。
目前Java體系中主要還是采用前端編譯+JIT編譯的方式
運(yùn)作過程:
首先通過前端編譯把符合Java語言規(guī)范的程序代碼轉(zhuǎn)化為滿足JVM規(guī)范所要求Class格式。
然后程序啟動時Class格式文件發(fā)揮作用,解釋執(zhí)行,省去編譯時間,加快啟動速度。
針對Class解釋執(zhí)行效率低的問題,在運(yùn)行中收集性能監(jiān)控信息,得知"熱點(diǎn)代碼"。
JIT逐漸發(fā)揮作用,把越來越多的熱點(diǎn)代碼"編譯優(yōu)化成本地代碼,提高執(zhí)行效率。
透過字節(jié)碼指令看代碼細(xì)節(jié)
面試題
類文件結(jié)構(gòu)有幾個部分?
知道字節(jié)碼嗎?字節(jié)碼都有哪些?Integer x = 5; int y = 5; 比較 x == y 都經(jīng)歷哪些步驟?
首先在聲明Integer x的時候會調(diào)用Integer.valueOf方法,首先看下這個方法
public?static?Integer?valueOf(int?i)?{????????if?(i?>=?IntegerCache.low?&&?i?<=?IntegerCache.high)
????????????return?IntegerCache.cache[i?+?(-IntegerCache.low)];
????????return?new?Integer(i);
}
如果所傳入的值是-128~127之間,就只用靜態(tài)內(nèi)部類的cache數(shù)組,否則就new一個新的對象。
這也表明為什么 Integer x = 128; Integer y = 128; x != y 的原因。
之后可以看到調(diào)用了 Integer.intValue 進(jìn)行自動拆箱操作,使得 x 變成 int 類型進(jìn)行比較。
虛擬機(jī)的基石:Class文件
字節(jié)碼文件里是什么?
源代碼經(jīng)過編譯器編譯之后便生成一個字節(jié)碼文件,字節(jié)碼是一種二進(jìn)制的類文件,它的內(nèi)容是JVM的指令,而不像C、C++經(jīng)由編譯器直接生成機(jī)器碼。
什么是字節(jié)碼指令?
Java虛擬機(jī)的指令,由一個字節(jié)長度的、代表著某種特定操作含義的操作碼(opcode),以及跟隨其后的零至多個代表此操作所需參數(shù)的操作數(shù)(operand)所構(gòu)成。虛擬機(jī)中許多指令并不包含操作數(shù),只有一個操作碼。
比如 aload_1 (操作碼) 、aload 4 (操作碼 + 操作數(shù)),因?yàn)閍load只有0、1、2、3所以如果想繼續(xù)擴(kuò)充,就要用到操作數(shù)。
image
Class文件結(jié)構(gòu)
Class類的本質(zhì)
由于一個Class文件都對應(yīng)著唯一一個類或接口的定義信息,但反過來說,Class文件實(shí)際上它并不一定以磁盤文件形式存在。Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流。
Class文件格式
Class的結(jié)構(gòu)不像 XML 等描述語言,由于它沒有任何分割符號,所以這其中的數(shù)據(jù)項(xiàng),無論是字節(jié)順序還是數(shù)量,都是被嚴(yán)格限定的,哪個字節(jié)代表什么含義、長度多少、先后順序,都不允許改變。
Class文件格式采用一種類似于C語言結(jié)構(gòu)體的方式進(jìn)行數(shù)據(jù)存儲,這種結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
無符號數(shù)屬于基本數(shù)據(jù)類型,以 u1 、u2 、u4、u8 來分別代表1個字節(jié)、2個字節(jié)、4個字節(jié)和8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
表是由多個無符號數(shù)或者其它表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表都習(xí)慣性地以"_info"結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)就是一張表,由于表沒有固定長度,所以通常會其前面加上個數(shù)說明。
Class文件結(jié)構(gòu)概述
Class文件的結(jié)構(gòu)并不是一成不變的,隨著Java虛擬機(jī)的不斷發(fā)展,總是不可避免地會對Class文件結(jié)構(gòu)做出一些調(diào)整,但是基本結(jié)構(gòu)和框架是非常穩(wěn)定的。
結(jié)構(gòu)如下:
魔數(shù)
Class文件版本
常量池
訪問標(biāo)志
類索引,父類索引,接口索引集合
字段表集合
方法表集合
屬性表集合
image類型名稱說明長度數(shù)量 u4 magic 魔數(shù),識別Class文件格式 4個字節(jié) 1 u2 minor_version 副版本號(小版本) 2個字節(jié) 1 u2 major_version 主版本號(大版本) 2個字節(jié) 1 u2 constant_pool_count 常量池計數(shù)器 2個字節(jié) 1 cp_info constant_pool 常量池表 n個字節(jié) constant_pool_count-1 u2 access_flags 訪問標(biāo)識 2個字節(jié) 1 u2 this_class 類索引 2個字節(jié) 1 u2 super_class 父類索引 2個字節(jié) 1 u2 interfaces_count 接口計數(shù)器 2個字節(jié) 1 u2 interfaces 接口索引集合 2個字節(jié) interfaces_count u2 fields_count 字段計數(shù)器 2個字節(jié) 1 field_info fields 字段表 n個字節(jié) fields_count u2 methods_count 方法計數(shù)器 2個字節(jié) 1 method_info methods 方法表 n個字節(jié) methods_count u2 attributes_count 屬性計數(shù)器 2個字節(jié) 1 attribute_info attributes 屬性表 n個字節(jié) attributes_count
字節(jié)碼文件解析
首先我們創(chuàng)建一個簡單的源碼:
public?class?Demo?{????private?int?num?=?1;
????public?int?add()?{
????????num?=?num?+?2;
????????return?num;
????}
}
通過 javac 命令編譯后可以看到Class文件內(nèi)容:
public?class?Demo?{????private?int?num?=?1;
????public?Demo()?{
????}
????public?int?add()?{
????????this.num?+=?2;
????????return?this.num;
????}
}
為什么編譯以后增加了無參構(gòu)造器,以及this關(guān)鍵字,我們一點(diǎn)點(diǎn)分析。
之后我們使用Notepad++,需要安裝一個HEX-Editor插件來打開這個Class文件就可以看到下圖內(nèi)容。
image這里就直接使用 excel 來清晰解釋具體內(nèi)容。
image魔數(shù):Class文件的標(biāo)志
每個Class文件開頭的4個字節(jié)的無符號整數(shù)成為魔數(shù)(Magic Number)
它的唯一作用就是確定這個文件是否為一個能被虛擬機(jī)接受的有效合法的Class文件。即:魔數(shù)是Class文件的標(biāo)識符。
魔數(shù)值固定為0xCAFEBABE
如果一個Class文件不以0xCAFEBABE開頭,虛擬機(jī)在進(jìn)行文件校驗(yàn)的時候就會直接拋出ClassFormatError錯誤。
使用魔數(shù)而不是擴(kuò)展名來識別主要是基于安全方面考慮,因?yàn)槲募U(kuò)展名是可以隨意改變的。
Class文件版本號
緊接著魔數(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編譯器的對應(yīng)關(guān)系表如下:
主版本(十進(jìn)制)副版本(十進(jìn)制)編譯器版本 45 3 1.1 46 0 1.2 47 0 1.3 48 0 1.4 49 0 1.5 50 0 1.6 51 0 1.7 52 0 1.8 53 0 1.9 54 0 1.10 55 0 1.11 Java的版本號是從45開始的,JDK 1.1 之后的每個JDK大版本發(fā)布主版本號向上加1。
不同版本的Java編譯器編譯的Class文件對應(yīng)的版本是不一樣的。目前,高版本的Java虛擬機(jī)可以執(zhí)行低版本編譯器編譯的Class文件,但是低版本的Java虛擬機(jī)不能執(zhí)行由高版本編譯器生成的Class文件。否則JVM會拋出java.lang.UnsupportedClassVersionError異常。
常量池:存放所有常量
常量池是Class文件中內(nèi)容最為豐富的區(qū)域之一。常量池對于Class文件中的字段和方法解析有著至關(guān)重要的作用。
隨著Java虛擬機(jī)的不斷發(fā)展,常量池的內(nèi)容也日漸豐富。可以說,常量池是整個Class文件的基石。
在版本號之后,緊跟著的就是常量池的數(shù)量,以及若干個常量池表項(xiàng)。
常量池中常量的數(shù)量是不固定的,所以需要一個u2類型的無符號數(shù),代表著常量池容量計數(shù)器(constant_pool_count),與Java語言習(xí)慣不一樣的是,容量計數(shù)器是從1開始而不是0。
常量池表項(xiàng)中,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時常量池中存放。
常量池計數(shù)器
由于常量池的數(shù)量不固定,所以需要放置兩個字節(jié)來表示常量池容量計數(shù)器。
常量池計數(shù)器(u2類型):從1開始,表示常量池中有多少項(xiàng)常量。即constant_pool_count = 1表示常量池中有0個常量池。
我們看剛才舉例的Demo:其值為0x0016,轉(zhuǎn)換為十進(jìn)制也就是22。
image但是實(shí)際中只有21項(xiàng)常量,范圍是1-21。
這里的常量池把第0項(xiàng)空出來了,為了滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達(dá)"不引用任何一個常量池項(xiàng)"的含義,這種情況可以使用索引0來表示。
常量池表
constant_pool是一種表及結(jié)構(gòu),以1 ~ constant_pool_count - 1 為索引。表明后面有多少個常量項(xiàng)。
常量池主要存放放兩大類變量:字面量(Literal)和符號引用(Symbolic Reference)。
它包含了Class文件結(jié)構(gòu)及其子結(jié)構(gòu)中引用的所有字符串常量、類、或接口名、字段名、和其它常量。常量池中的每一項(xiàng)都具有相同特征。第1個字節(jié)作為類型標(biāo)記,用于確定該項(xiàng)的格式,這個字節(jié)成為tag byte (標(biāo)記字節(jié))。
字面量和符號引用
常量池主要存放放兩大類變量:字面量(Literal)和符號引用(Symbolic Reference)。
| 字面量 | 文本字符串 |
| 聲明為final的常量值 | |
| 符號引用 | 類和接口的全限定名 |
| 字段和名稱的描述符 | |
| 方法的名稱和描述符 |
全限定名
com/test/Demo這個就是類的全限定名,僅僅是把包名的"."替換成了"/",為了使連續(xù)的多個全限定名之間不產(chǎn)生混淆,在使用時最后一般會加入一個";"表示全限定名結(jié)束。
簡單名稱
簡單名稱是指沒有類型和參數(shù)修飾的方法或者字段名稱,上面例子中的類的add()方法,和num字段的簡單名稱分別是add和num。
描述符
描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(boolean,byte,char,short,int,float,long,double)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示:
| B | 基本數(shù)據(jù)類型byte |
| C | 基本數(shù)據(jù)類型char |
| D | 基本數(shù)據(jù)類型double |
| F | 基本數(shù)據(jù)類型float |
| I | 基本數(shù)據(jù)類型int |
| J | 基本數(shù)據(jù)類型long |
| S | 基本數(shù)據(jù)類型short |
| Z | 基本數(shù)據(jù)類型boolean |
| V | 代表void類型 |
| L | 對象類型,比如:Ljava/lang/Object; |
| [ | 數(shù)組類型,代表一維數(shù)組。比如:double[][][] = [[[D |
????????Object[]?arr?=?new?Object[10];
????????System.out.println(arr);//[Ljava.lang.Object;@14ae5a5
????????Long[][]?longs?=?new?Long[10][10];
????????System.out.println(longs);//[[Ljava.lang.Long;@7f31245a
????????int[][]?ints?=?new?int[10][10];
????????System.out.println(ints);//[[I@7f31245a
}
需要注意的是,用描述符來描述方法的時候,先參數(shù)列表后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號"()"內(nèi)。
虛擬機(jī)在加載Class文件時才會進(jìn)行動態(tài)鏈接,也就是說,Class文件中不會保存各個方法和字段的最終內(nèi)存布局信息,因此,這些字段和方法的符號引用不經(jīng)過轉(zhuǎn)換是無法直接被虛擬機(jī)使用的。當(dāng)虛擬機(jī)運(yùn)行時,需要從常量池中獲得對應(yīng)的符號引用,再在類加載過程中的解析階段將其替換為直接引用,并翻譯到具體的內(nèi)存地址中。
符號引用:符號引用以一組符號來描述所引用的目標(biāo),符號可以是任何形式的字面量,只要使用時無歧義地定位到目標(biāo)即可。符號引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到了內(nèi)存中。
直接引用:直接引用可以是直接指向目標(biāo)的指針、相對偏移量或是一個能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個符號引用在不同虛擬機(jī)實(shí)例上翻譯出來一般不會相同。如果有了直接引用,則說明引用的目標(biāo)必定存在內(nèi)存之中。
常量類型和結(jié)構(gòu)
| CONSTANT_utf8_info | 1 | UTF-8編碼的字符串 |
| CONSTANT_Integer_info | 3 | 整型字面量 |
| CONSTANT_Float_info | 4 | 浮點(diǎn)型字面量 |
| CONSTANT_Long_info | 5 | 長整型字面量 |
| CONSTANT_Double_info | 6 | 雙精度浮點(diǎn)型字面量 |
| CONSTANT_Class_info | 7 | 類或接口的符號引用 |
| CONSTANT_String_info | 8 | 字符串類型字面量 |
| CONSTANT_Fieldref_info | 9 | 字段的符號引用 |
| CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
| CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
| CONSTANT_NameAndType_info | 12 | 字段或方法的符號引用 |
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_MethodType_info | 16 | 標(biāo)志方法類型 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一個動態(tài)方法調(diào)用點(diǎn) |
從上面的圖中可以看到,雖然每一項(xiàng)的結(jié)構(gòu)都不相同,但是它們有個共同點(diǎn),就是每一項(xiàng)的第一個字節(jié)都是一個標(biāo)志位,標(biāo)識這一項(xiàng)是哪種類型的常量。
訪問標(biāo)記(access_flag)
在常量池后,緊接著訪問標(biāo)記,該標(biāo)記使用兩個字節(jié)表示,用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為 public 類型;是否定義為 abstract 類型;如果是類的話,是否被聲明為 final 等,詳細(xì)說明如下:
標(biāo)志名稱標(biāo)志值含義 ACC_PUBLIC 0x0001 標(biāo)志為public類型 ACC_FINAL 0x0010 標(biāo)志被聲明為final,只有類可以設(shè)置 ACC_SUPER 0x0020 標(biāo)志允許使用invokespecial字節(jié)碼指令的新語義,JDK1.0.2之后編譯出來的類的這個標(biāo)志默認(rèn)為真。(使用增強(qiáng)的方法調(diào)用父類方法) ACC_INTERFACE 0x0200 標(biāo)志這是一個接口 ACC_ABSTRACT 0x0400 是否為abstract類型,對于接口或者抽象類來說,次標(biāo)志值為真,其他類型為假 ACC_SYNTHETIC 0x1000 標(biāo)志此類并非由用戶代碼產(chǎn)生(即:由編譯器產(chǎn)生的類,沒有源碼對應(yīng)) ACC_ANNOTATION 0x2000 標(biāo)志這是一個注解 ACC_ENUM 0x4000 標(biāo)志這是一個枚舉 類的訪問權(quán)限通常為 ACC_ 開頭的常量。
每一種類型的表示都是通過設(shè)置訪問標(biāo)記的32位中的特定位來實(shí)現(xiàn)的,比如如果是public final的類,則標(biāo)記為 ACC_PUBLIC | ACC_FINAL。
使用ACC_SUPER可以讓類更準(zhǔn)確的定位到父類的方法super.method(),默認(rèn)都是設(shè)置并使用這個標(biāo)記。
我們可以看到上面的Demo的字節(jié)碼對應(yīng)的訪問標(biāo)記是21,也就是對應(yīng)表格中的 ACC_PUBLIC 和 ACC_SUPER 加起來就等于21。
類索引、父類索引、接口索引集合
| u2 | this_class |
| u2 | super_class |
| u2 | interfaces_count |
| u2 | interfaces[interfaces_count] |
類索引用于確定這個類的繼承關(guān)系。
父類索引用于確定這個類的父類全限定名,由于Java語言不允許多重繼承,所以父類索引只有一個,除了Object 之外,所有的Java類都有父類,因此除了Object之外,所有Java類的父類索引都不為0。
接口索引集合就用來描述這個類實(shí)現(xiàn)了拿些接口,這些被實(shí)現(xiàn)的接口將按implements語句(如果當(dāng)前類本身是一個接口,則應(yīng)當(dāng)是 extends 語句)后的接口順序從左到右排列在接口索引集合中。
this_class(類索引)
2 字節(jié)無符號整數(shù),指向常量池的索引。它提供了類的全限定名,如com/test/Demo。this_class的值必須是常量池中某項(xiàng)的一個有效索引值。常量池在這個索引出的成員必須為constant_class_info類型結(jié)構(gòu)體,該結(jié)構(gòu)表示這個Class文件所定義的類或者接口。
super_class(父類索引)
2字節(jié)無符號整數(shù),指向常量池的索引。它提供了當(dāng)前類的父類全限定名。如果我們沒有繼承任何類,其默認(rèn)繼承的是Java/lang/Object類。同時,由于Java不支持多繼承,所以其父類只有一個。
super_class指向的父類不能是final類型。
interfaces
指向常量池索引集合,它提供了一個符號引用到所有已實(shí)現(xiàn)的接口。
由于一個類可以實(shí)現(xiàn)多個接口,因此需要以數(shù)組形式保存多個接口的索引,表示接口的每個索引也是一個指向常量池的Constant_Class(指向的是接口,并不是類)。
interfaces_count(接口計數(shù)器)
interfaces_count 項(xiàng)的值表示當(dāng)前類或者接口的直接超接口數(shù)量。
interfaces[](接口索引集合)
interfaces[]中每個成員的值必須是對常量池表中某項(xiàng)索引的有效索引值,它的長度為interfaces_count。每個成員interfaces[i]必須為constant_class_info結(jié)構(gòu),其中 0 <= i < interfaces_count。在interfaces[]中,各成員所表示的接口順序?qū)?yīng)的源代碼中給定的接口順序(從左至右)一樣,即 interface[0]對應(yīng)的是源代碼中最左邊的接口。
字段表集合
用于描述接口或類中聲明的變量。字段(field)包括類級變量以及實(shí)例變量,但是不包括方法內(nèi)部、代碼塊內(nèi)部聲明的局部變量。
字段叫什么名字,字段被定義為什么數(shù)據(jù)類型,這些都是無法固定的。只能引用常量池中的常量來描述。
它指向常量池索引集合,它描述了每個字段的完整信息。比如字段的標(biāo)識符、訪問修飾符(public、private或protected)、是類變量還是實(shí)例變量(static修飾符)、是否是常量(final修飾符)等。
注意:
字段表集合中不會列出從父類或者實(shí)現(xiàn)的接口中繼承來的字段,但有可能列出原本Java代碼之中不存在的字段。譬如在內(nèi)部類中為了保持對外部類的訪問性,會自動添加指向外部類實(shí)例的字段。
在Java語言中字段是無法重載的,兩個字段的數(shù)據(jù)類型,修飾符不管是否相同,都必須使用不一樣的名稱,但是對于字節(jié)碼來說,如果兩個字段的描述符不一致,那字段名重名也是合法的。
fields_count(字段計數(shù)器)
fields_count的值表示當(dāng)前Class文件fields表的成員個數(shù),用2個字節(jié)表示。
fields表中每個成員都是一個field_info結(jié)構(gòu),用于表示該類或接口所聲明的所有字段或者實(shí)例字段,不包括方法內(nèi)部聲明的變量,也不包括從父類或父接口繼承的那些字段。
fields[](字段表)
fields表中的每個成員都必須是一個field_info結(jié)構(gòu)的數(shù)據(jù)項(xiàng),用于表示當(dāng)前類或接口中某個字段的完整描述。
一個字段的信息包括如下這些信息。這些信息中,各個修飾符都是布爾值,要么有,要么沒有。
作用域
實(shí)例變量還是類變量
是否final
是否volatile
是否序列化 transient 修飾
字段數(shù)據(jù)類型(基本數(shù)據(jù)類型,對象,數(shù)組)
字段名稱
字段表結(jié)構(gòu)
| u2 | access_flags | 訪問標(biāo)志 | 1 |
| u2 | name_index | 字段名索引 | 1 |
| u2 | descriptor_index | 描述符索引 | 1 |
| u2 | attributes_count | 屬性計數(shù)器 | 1 |
| attribute_info | attributes | 屬性集合 | attributes_count |
字段表訪問標(biāo)識
我們知道,一個字段可以被各種關(guān)鍵字修飾,比如作用域修飾符、static修飾符、final修飾符、volatile修飾符等等。字段訪問標(biāo)志有如下這些:
| ACC_PUBLIC | 0x0001 | 字段是否為public |
| ACC_PRIVATE | 0x0002 | 字段是否為private |
| ACC_PROTECTED | 0x0004 | 字段是否為protected |
| ACC_STATIC | 0x0008 | 字段是否為static |
| ACC_FINAL | 0x0010 | 字段是否為final |
| ACC_VOLATILE | 0x0040 | 字段是否為volatile |
| ACC_TRANSTENT | 0x0080 | 字段是否為transient |
| ACC_SYNCHETIC | 0x1000 | 字段是否為由編譯器自動產(chǎn)生 |
| ACC_ENUM | 0x4000 | 字段是否為enum |
字段名索引
根據(jù)字段名索引的值,查詢常量池中的指定索引項(xiàng)即可。
描述符索引
描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型及無返回值的void都是用大寫符來表示,對象使用字符L+全限定名表示。
屬性集合
一個字段還可能擁有一些屬性,用于存儲更多的額外信息。比如初始化值、一些注釋信息等。屬性個數(shù)存放在attribute_count中,屬性具體內(nèi)容存在attributes數(shù)組中。
結(jié)構(gòu)為:
ConstantValue_attribute{????u2?attribute_name_index;
????u4?attribute_length;
????u2?constantvalue_index;
}
對于常量屬性而言,attribute_length的值恒為2。
image常量值索引所指向的 #8 其實(shí)就是對應(yīng)int的值。
image方法表集合
methods:指向常量池索引集合,它完整描述了每個方法的簽名。
在字節(jié)碼文件中,每一個method_info項(xiàng)都對應(yīng)著一個類或者接口中的方法信息。比如方法的訪問修飾符,方法的返回值類型,以及方法的參數(shù)信息等。
如果方法不是抽象的或者不是native的,那么字節(jié)碼就會體現(xiàn)出來。
一方面,methods表只描述當(dāng)前類或接口中聲明的方法,不包括從父類或父接口繼承的方法。另一方面,methods表有可能會出現(xiàn)由編譯器自動添加的方法,最典型的就是編譯器產(chǎn)生的方法信息,比如類或者接口初始化方法()和實(shí)例初始化方法()。
methodds_count(方法計數(shù)器)
methods_count的值表示當(dāng)前class文件methods表的成員個數(shù)。使用 2 個字節(jié)來表示。
methods表中每個成員都是一個method_info結(jié)構(gòu)。
methods[](方法表)
methods表中的每個成員都必須是一個method_info結(jié)構(gòu),用于表示當(dāng)前類或接口中某個方法的完整描述。如果某個method_info結(jié)構(gòu)的access_flags項(xiàng)既沒有設(shè)置ACC_NATIVE標(biāo)志和ACC_ABSTRACT標(biāo)志,那么該結(jié)構(gòu)中也應(yīng)該包含實(shí)現(xiàn)這個方法所用到的Java虛擬機(jī)指令。
method_info結(jié)構(gòu)可以表示類和接口中定義的所有方法,包括實(shí)例方法、類方法、實(shí)例初始化方法和類或接口初始化方法。
方法表的結(jié)構(gòu)實(shí)際和字段表是一致的。如下:
| u2 | access_flags | 訪問標(biāo)志 | 1 |
| u2 | name_index | 字段名索引 | 1 |
| u2 | descriptor_index | 描述符索引 | 1 |
| u2 | attributes_count | 屬性計數(shù)器 | 1 |
| attribute_info | attributes | 屬性集合 | attributes_count |
屬性表集合
方法表集合之后的屬性表集合,指的是class文件所攜帶的輔助信息,比如該Class文件的源文件的名稱,以及任何帶有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。這類信息通常被用于Java虛擬機(jī)的驗(yàn)證和運(yùn)行,以及Java程序的調(diào)試。
此外,字段表、方法表都可以有自己的屬性表。用于描述某些場景專有的信息。
屬性表集合的限制沒有那么嚴(yán)格,不再要求各個屬性表具有嚴(yán)格的順序,并且只要不與已有的屬性名重復(fù),任何人實(shí)現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息,但Java虛擬機(jī)運(yùn)行時會忽略掉它不認(rèn)識的屬性。
屬性的通用格式
屬性表的結(jié)構(gòu)比較靈活,各種不同的屬性只要滿足以下結(jié)構(gòu)即可:
| u2 | attribute_name_index | 1 | 屬性名索引 |
| u4 | attribute_length | 1 | 屬性長度 |
| u1 | info | attribute_length | 屬性表 |
屬性類型
| Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
| ConstantValue | 字段表 | final關(guān)鍵字定義的常量池 |
| Deprecated | 類,方法,字段表 | 被聲明為deprecated的方法和字段 |
| Exceptions | 方法表 | 方法拋出的異常 |
| EnclosingMethod | 類文件 | 僅當(dāng)一個類為局部類或者匿名類是才能擁有這個屬性,這個屬性用于標(biāo)識這個類所在的外圍方法 |
| InnerClass | 類文件 | 內(nèi)部類列表 |
| LineNumberTable | Code屬性 | Java源碼的行號與字節(jié)碼指令的對應(yīng)關(guān)系 |
| LocalVariableTable | Code屬性 | 方法的局部變量描述 |
| StackMapTable | Code屬性 | JDK1.6中新增的屬性,供新的類型檢查檢驗(yàn)器檢查和處理目標(biāo)方法的局部變量和操作數(shù)有所需要的類是否匹配 |
| Signature | 類,方法表,字段表 | 用于支持泛型情況下的方法簽名 |
| SourceFile | 類文件 | 記錄源文件名稱 |
| SourceDebugExtension | 類文件 | 用于存儲額外的調(diào)試信息 |
| Synthetic | 類,方法表,字段表 | 標(biāo)志方法或字段為編譯器自動生成的 |
| LocalVariableTypeTable | 類 | 使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數(shù)化類型而添加 |
| RuntimeVisibleAnnotations | 類,方法表,字段表 | 為動態(tài)注解提供支持 |
| RuntimeInvisibleAnnotations | 表,方法表,字段表 | 用于指明哪些注解是運(yùn)行時不可見的 |
| RuntimeVisibleParameterAnnotation | 方法表 | 作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象為方法 |
| RuntimeInvisibleParameterAnnotation | 方法表 | 作用與RuntimeInvisibleAnnotations屬性類似,作用對象哪個為方法參數(shù) |
| AnnotationDefault | 方法表 | 用于記錄注解類元素的默認(rèn)值 |
| BootstrapMethods | 類文件 | 用于保存invokeddynamic指令引用的引導(dǎo)方式限定符 |
Code屬性
Code屬性就是存放方法體里面的代碼,像接口或者抽象方法,沒有具體的方法體,因此也就不會有Code屬性了。
Code屬性表的結(jié)構(gòu):
| u2 | attribute_name_index | 1 | 屬性名索引 |
| u4 | attribute_length | 1 | 屬性長度 |
| u2 | max_stack | 1 | 操作數(shù)棧深度的最大值 |
| u2 | max_locals | 1 | 局部變量表所需的存續(xù)空間 |
| u4 | code_length | 1 | 字節(jié)碼指令的長度 |
| u1 | code | code_length | 存儲字節(jié)碼指令 |
| u2 | exception_table_length | 1 | 異常表長度 |
| exception_info | exception_table | exception_length | 異常表 |
| u2 | attributes_count | 1 | 屬性集合計數(shù)器 |
| attribute_info | attributes | attributes_count | 屬性集合 |
LineNumberTable屬性
LineNumberTable屬性是可選變長屬性,位于Code結(jié)構(gòu)的屬性表
LineNumberTable屬性是用來描述Java源碼行號與字節(jié)碼行號之間的對應(yīng)關(guān)系。
start_pc,即字節(jié)碼行號;line_number,即Java源代碼的行號
在Code屬性的屬性表中,LineNumberTable屬性可以按照任意順序出現(xiàn),此外,多個LineNumberTable屬性可以共同表示一個行號在源文件中表示的內(nèi)容,即LineNumberTable屬性不需要與源文件的行一一對應(yīng)。
LineNumberTable屬性表結(jié)構(gòu)
LineNumberTable_attribute?{?????u2?attribute_name_index;??
???u4?attribute_length;??
???u2?line_number_table_length;??
???{???u2?start_pc;??
???????u2?line_number;??
???}?line_number_table[line_number_table_length];??
}?
| u2 | attribute_name_index | 1 | 屬性名索引 |
| u4 | attribute_length | 1 | 屬性長度 |
| u2 | line_number_table_length | 1 | 行號表長度 |
| line_number_info | line_number_table | line_number_table_length | 行號表 |
SourceFile屬性
| u2 | attribute_name_index | 1 | 屬性名索引 |
| u4 | attribute_length | 1 | 屬性長度 |
| u2 | sourcefile_index | 1 | 源碼文件索引 |
其長度總是固定的8個字節(jié)。
image我們看之前的Excel最后一位也可以看到對應(yīng)的就是源文件名。
總結(jié)
以上是生活随笔為你收集整理的php编译成二进制文件_JVM字节码文件概述的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python输入文字、成为字典_Pyth
- 下一篇: jquery获取浏览器版本号_前端为什么