代碼編譯的結果從本地機器碼轉爲字節碼;
無關性的基石:
各個不一樣平臺的虛擬機和全部平臺統一使用的程序存儲格式-字節碼,是構成平臺無關性的基石;
實現語言無關性的基礎仍然是虛擬機和字節碼存儲格式;
Java虛擬機不和包括Java在內的任何語言綁定,它只和class文件這種特定的二進制文件所關聯;
Class文件中包含了Java虛擬機指令集和符號表以及若干其餘輔助信息。
基於安全方面的考慮,Java虛擬機規範要求在Class文件中使用許多強制性的語法和機構化約束,但任何一門功能性語言均可以表示爲一個能被Java虛擬機所接受的有效的Class文件。
Class類文件的結構
Class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎所有是程序運行的必要數據,沒有空隙存在。
當遇到須要佔用8個字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8位字節進行存儲。
根據Java虛擬機規範的規定,Class文件格式採用一種相似於C語言結構體的僞結構來存儲數據,這種
僞結構中只有兩種數據類型:無符號數和表
。
無符號數
:
無符號數屬於基本數據類型,以u一、u二、u四、u8分別表明1個字節、2個字節、4個字節和8個字節的無符號數;
無符號數能夠用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
表:
表是由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣以「
_info
」爲結尾。
表用於描述有層次關係的複合結構的數據,整個Class文件本質上就是一張表;
Class結構不像XML等描述語言,因爲它沒有任何分隔符號;
魔數和Class文件版本:
魔數(magic)
:每一個Class文件的頭4個字節成爲魔數,它惟一的做用是肯定整個文件是否爲一個能被虛擬機接受的Class文件。
Class文件的魔數值是
0xCAFEBABE
Class文件版本
:第5和第6個字節是次版本號(Minor Version)。第7和第8個字節是主版本號(Major Version)。
Java的版本號是從45開始的,JDK1.1以後的每一個JDK大版本發佈主版本號向上加1,高版本的JDK能向下兼容之前版本的Class文件,但不能運行之後版本的Class文件。(即便文件格式並未發生任何改變,虛擬機也必須拒絕執行超過其版本號的Class文件)。
常量池:
緊挨着主次版本號的就是常量池入口;
常量池能夠理解爲Class文件之中的資源倉庫,它是Class文件結構中與其餘項目關聯最多的數據類型,也是佔用Class文件空間最大的數據項目之一,同時它仍是在Class文件中第一個出現的表類型數據項目。
常量池容量計數器:
由於常量池中常量的數量不固定,因此在常量池的入口須要放置一項u2類型的數據,表明
常量池容量計數器,是從1開始計數
。
常量池容量計數器,0x0016,十進制爲22,表明有21個常量。
Class文件結構中只有常量池的容量計數是從1開始,對於其餘集合類型,包括接口索引集合、字段表集合、方法表集合等的容量計數都是與通常習慣相同,從0開始計數。
常量池
中主要存放兩大類常量:
字面量(Literal)和符號引用(Symbolic References)
;
字面量
:比較接近於Java語言層面的常量概念,如文本字符串、聲明爲final的常量值等;
符號引用
則屬於編譯原理方面的概念,包括下面三類常量:
一、類和接口的全限定名;
二、字段的名稱和
描述符
三、方法的名稱和描述符
當虛擬機運行時,須要從常量池得到對應的符號引用,再在類建立時或運行時解析、翻譯到具體的內存地址之中。
常量池中每一項常量都是一個表,共有11種,表開始的第一位都是一個本身的標誌位,代表這個常量屬於哪一種類型。
Java程序中不能定義超過64KB英文字符的變量和方法名,不然沒法編譯。
使用JAVAP工具能夠分析Class文件字節碼:javap -verbose TestClass
訪問標誌:
在常量池結構以後,緊接着的2個字節表明訪問標誌(access_flags),
這個標誌用於識別一些類或者接口層次的訪問信息
;
包括:這個Class是類仍是接口;是否認義爲public類型;是否認義爲abstract類型;若是是類,是否被聲明爲final類型等;
訪問標誌中一共有16個標誌位可使用;
類索引、父類索引與接口索引集合:用來肯定集成關係
類索引
:一個u2類型的數據,用於肯定整個類的全限定名。
父類索引
:一個u2類型的數據,用於肯定這個類的父類的全限定名。因爲Java不容許多重集成,因此父類索引只有一個。
接口索引集合
:一組u2類型的數據集合,用於描述這個類實現了哪些接口。
對於接口索引集合,入口的第一項-u2類型的數據爲接口計數器,表示索引表的容量。若是該類沒有實現任何接口,則該接口計數器值爲0,後面接口的索引表再也不佔用任何字節;
字段表集合
字段表(field_info),用於描述接口或者類中聲明的變量。
字段(field)包括類級變量以及實例級變量,可是不包含方法內聲明的局部變量。
描述該字段是不是public、private、protected、static、final、volatile等;
簡單名稱
:指沒有類型和參數修飾的方法或者字段名稱;
描述符
:用來描述字段的數據類型,方法的參數列表和返回值。
基本數據類型以及表明無返回值的void類型用一個大寫字母表示;
對象類型用字符L加對象的全限定名來表示;
方法表集合:描述方法
Class文件存儲格式中對方法的描述和對字段的描述幾乎採用了徹底一致的方式,方法表的結構如同字段表同樣。
包括訪問標誌、名稱索引、描述符索引、屬性表集合;
方法中的代碼存儲在方法屬性表集合中一個名爲Code的屬性中;
在Java虛擬機規範中,要重載一個方法,除了要與原方法具備相同的簡單名稱外,還要求必須擁有一個與原方法不一樣的特徵簽名,特徵簽名在java代碼層面和字節碼層面有不一樣的定義。
代碼層面的簽名只包括方法名稱,參數順序以及參數類型;
字節碼正面還包括方法返回值和異常表。
所以在Class文件中,若是兩個方法有相同的名稱和特徵簽名,可是返回值不一樣,也是合法的。
屬性表集合
屬性表集合的限制較少,不要求各個屬性表有嚴格的順序,而且只要不和已有的屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息;
Java虛擬機在運行時會忽略掉它不認識的屬性。
Code屬性
Java程序方法體中的代碼通過javac編譯器處理後,最終變爲字節碼指令存儲在Code屬性內。
max_stack:
虛擬機運行的時候會根據它的值來分配棧幀中的操做棧深度;
max_locals:單位是Slot,Slot是虛擬機爲局部變量分配內存所使用的最小單位;
code_length+code:用來存儲Java源程序編譯後生成的字節碼指令;
code_length:表示字節碼長度
code:用於存儲字節碼的一系列字節流。
虛擬機規範中明確限制一個方法不容許超過
65535
條字節碼指令。若是超過了,Java編譯器也會拒絕編譯。
在任何實例方法中,均可以經過this關鍵字訪問到此方法所屬對象,它是經過Javac編譯器編譯的時候把對this關鍵字的訪問轉變爲對一個普通方法參數的訪問。
Exceptions屬性
做用:列舉出方法中可能拋出的受查異常,也就是方法描述時在throws關鍵字後面列舉的異常;
LineNumberTable屬性
做用:用於描述java源碼行號與字節碼行號之間的對於關係。不是運行時必需的屬性,可是會默認生成到Class文件中;
javac中可使用-g:none取消,或使用
-g:lines
來要求生成這項信息。
LocalVariableTable屬性
做用:描述棧幀中的局部變量表中的變量與Java源碼中定義的變量之間的關係。不是運行時必需的屬性。
javac中可使用-g:none取消,或使用
-g:vars
來要求生成這項信息;
SourceFile屬性
做用:用於記錄生成這個Class文件的源碼文件名稱。可選。
javac中可使用-g:none取消,或使用
-g:source
來要求生成這項信息;
ConstactValue屬性
做用:通知虛擬機自動爲靜態變量賦值;只有被static關鍵字修飾的變量(類變量)纔可使用這個屬性;
對於非靜態變量,則是在實例構造器<init>方法中進行;
目前Sun Javac編譯器中,對於final修飾的靜態變量,而且這個變量的數據類型是基本數據類型或String,就使用ConstactValue屬性來進行初始化;
若是僅僅是靜態變量,未使用final修飾,那麼就是在實例構造器<clinit>方法中進行初始化;
虛擬機規範中並未要求字段必須設置ACC_FINAL標識。對final關鍵字的要求是javac編譯器本身加入的限制。
InnerClasses屬性
做用:用於記錄內部類與宿主類之間的關聯;
Deprecated及Synthetic屬性
Deprecated做用:表示某個類、字段或方法,已經被程序做者定位再也不推薦使用。
Synthetic做用:表示此字段或方法並非由Java編碼直接產生的,而是由編譯器自行添加的。JDK1.5之後;
如this字段和實例構造器,類構造器等;
StackMapTable屬性
JDK1.6發佈後增長到Class文件規範中的,是一個複雜的變長屬性,位於Code屬性的屬性表中。
Signature屬性
JDK1.5發佈後增長到Class文件規範中的,是一個可選的定長屬性,能夠出現於類、屬性表和方法表結構的屬性表中;
BootstrapMethods屬性
JDK1.7發佈後增長到Class文件規範中的,是一個複雜的變長屬性,位於類文件的屬性表中
字節碼指令簡介
Java虛擬機的指令由一個字節長度的,表明着某種特定操做含義的數字(操做碼)以及跟隨其後的零至多個表明此操做所需參數(操做數)而構成。
因爲Java虛擬機採用面向操做數棧而不是寄存器的架構,因此大多數的指令都不包含操做數,只有一個操做碼。
字節碼指令集的優缺點:
缺點
:
一、因爲限制Java虛擬機操做碼的長度是一個字節(0~255),意味着指令集的操做碼總數不超過256條;
二、因爲Class文件格式放棄了編譯後代碼的操做數長度對齊,意味着虛擬機處理那些超過一個字節數據的時候,不得不在運行時從字節中重建出具體數據的結構;
優勢:
一、放棄操做數長度對齊,意味着能夠省略不少填充和間隔符號;
二、用一個字節來表明操做碼,也是爲了儘量獲取短小精幹的編譯代碼;
這種追求儘量小數據量,高傳輸效率的設計是由Java語言涉及之初面向網絡、智能家電的技術背景決定的,並一直沿用到至今。
字節碼與數據類型
加載和存儲指令
做用:用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸;
這類指令包含以下以下內容:
一、將一個局部變量加載到操做棧;***load
二、將一個數值從操做數棧存儲到局部變量表;***store
三、將一個常量加載到操做數棧;***const
四、擴充局部變量表的訪問索引的指令:wide;
存儲數據的操做數棧和局部變量表主要就是由加載和存儲指令進行操做;
運算指令
運算或算數指令用於對兩個操做數棧上的值進行某種特定運算,並把結果從新存入到操做數棧頂。
算數大體分爲:對整型數據進行運算的指令和對浮點型數據進行運算的指令;
Java虛擬機要求在進行浮點數的運算時,全部的運算結果都必須舍入到適當的精度,舍入模式是IEEE754規範中的默認舍入模式,稱爲向最接近數舍入模式;處理的時候不會拋出任何運行時異常;
在將浮點數轉爲整數時,Java虛擬機使用IEEE754標準的向零舍入模式,結果會致使數字備截斷,全部小數部分的有效字節都會備丟棄;
類型轉換指令
類型轉換指令能夠將兩種不一樣的數值類型進行相互轉換,這些轉換操做通常用於實現用戶代碼中的顯式類型轉換操做。
寬化類型轉換、窄化類型轉換。
對象建立與訪問指令
對象建立後,就可使用對象訪問指令獲取對象實例或者數組實例中的字段或者數組元素。
指令以下:
2)建立數組的指令:newarray,anewarray,multianewarray
3)訪問字段指令:getfield,putfield,getstatic,putstatic
4)把數組元素加載到操做數棧指令:baload,caload,saload,iaload,laload,faload,daload,aaload
5)將操做數棧的數值存儲到數組元素中執行:bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
7)檢查實例類型指令:instanceof,checkcast
1)將操做數棧的棧頂一個或兩個元素出棧:pop、pop2
2)複製棧頂的一個或兩個數值並將複製值活着雙份的複製值從新壓入棧頂:dup、dup二、dup_x一、dup2_x1
控制轉移指令
讓JVM有條件或無條件從指定指令而不是控制轉移指令的下一條指令繼續執行程序。控制轉移指令包括:
1)條件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt等
2)複合條件分支:tableswitch,lookupswitch
3)無條件分支:goto,goto_w,jsr,jsr_w,ret
各類類型的比較最終都會轉化爲int類型的比較操做;
方法調用和返回指令
方法調用指令與數據類型無關,可是方法返回指令是根據返回值的類型區分的;
異常處理指令
在java程序中顯式拋出異常的操做是由athrow指令來實現;
同步指令
方法級的同步是隱式的,無需經過字節碼指令來控制,它實如今方法調用和返回操做中。
虛擬機從方法常量池中的方法標結構中的 ACC_SYNCHRONIZED標誌區分是不是同步方法。方法調用時,調用指令會檢查該標誌是否被設置,若設置,執行線程持有moniter,而後執行方法,最後完成方法時釋放moniter。
同步一段指令集序列,一般由synchronized塊標示,JVM指令集中有monitorenter和monitorexit來支持synchronized語義。
結構化鎖定是指方法調用期間每個monitor退出都與前面monitor進入相匹配的情形。JVM經過如下兩條規則來保證結結構化鎖成立(T表明一線程,M表明一個monitor):
1)T在方法執行時持有M的次數必須與T在方法完成時釋放的M次數相等
2)任什麼時候刻都不會出現T釋放M的次數比T持有M的次數多的狀況
虛擬機實現的方式主要有如下兩種:
一、將輸入的Java虛擬機代碼在加載或執行時翻譯成另一種虛擬機的指令集;
二、將輸入的Java虛擬機代碼在加載或執行時翻譯成宿主機CPU的本地指令集(JIT代碼生成技術)