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