JVM之類文件結構

           實現語言無關性的基礎仍然是虛擬機和字節碼存儲格式。Java虛擬機不和包括Java在內的任何語言綁定,它至於“Class”文件這種特定的二進制文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號表以及若干其它輔助信息。

       Java語言中的各種變量、關鍵字和運算符號的語義最終都是有多條字節碼指令組合而成,因此字節碼命令所能提供的語義描述能力肯定會比Java語言本身更加強大。

Class類文件的結構

       任何一個Class文件都對應着唯一一個類或接口的定義信息,但反過來說,類或接口並不一定都得定義在文件裏(比如類或接口可以通過類加載器直接生成)
       Class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這就使得Class文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。根據Java虛擬機規範的規定,Class文件格式採用一種類似於C語言結構體的爲結構來存儲數據,這種微結構中只有兩種數據類型:無符號數和表
     (1)無符號數
      無符號數屬於基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數,無符號數可以用來描述數字、索引、數量值或者按照UTF-8編碼構成字符串值。
     (2)表
      表是由多個無符號數或者其他表作爲數據項構成的符合數據類型,所有表都習慣性的以"_info"結尾。表用於描述有層次關係的符合結構的數據,整個Class文件本質上就是一張表,其構成成分就是如下的數據項:
類型 名稱 數量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attribute_count 1
attribute_info attributes attributes_count

      無論是無符號數還是表,當需要描述同一類型但數量不定的多個數據時,經常會使用一個前置的容量計數器加若干個連續的數據項的形式,這時稱這一系列連續的某一類型的數據爲某一類型的集合。Class文件的結構不象XML等描述語言,由於它沒有任何分隔符號,所以上面表格中的數據項,無論是順序還是數量,甚至於數據存儲的字節這樣的細節都是被閹割限定的,哪個字節代表什麼含義,長度多少,先後順序如何,都不允許改變。

魔數與Class文件的版本

      每個Class文件的頭4個字節稱爲魔數(Magic Number),他的唯一作用是確定這個文件是否爲一個能被虛擬機接受的Class文件。之所以用魔數來表示主要是因爲考慮到安全性,因爲拓展名很容易修改啊。Class文件的魔數值是:0xCAFEBABE(咖啡寶貝?)。緊接着魔數的4個字節是Class文件的版本號:第五和第六個字節是次版本號,第七和第八識主版本號,Java的版本號是從45開始的。

常量池    

       緊接着主次版本號之後的是常量池入口,常量池可以理解爲Class文件中的資源倉庫。他是Class文件結構中與其他項目關聯最多的數據類型,也是佔用Class文件空間最大的數據項之一,同時它還是在Class文件中第一個出現的表類型數據項目。
       由於常量池中常量的數量是不固定的,所以需要在常量池的入口放置一項u2類型的數據,代表常量池容量計數值,與Java中語言習慣不一樣的是,這個容量計數是從1開始而不是從0開始。常量池中主要存放兩大類常量:字面量(Literial)和符號引用(Symbolic Reference),字面量比較接近於Java語言層面的常量概念,比如文本字符串、聲明爲final的常量值等,而符號引用則屬於編譯原理方面的概念,包括下面三類常量。
      (1)類和接口的全限定名
      (2)字段的名稱和描述符
      (3)方法的名稱和描述符
      Java代碼在進行Javac編譯的時候,並不像C和C++那樣有連接這一步驟,而是在虛擬機加載Class文件的時候進行動態鏈接,也就是說,在Class文件中不會保存各個方法字段的最終內存佈局信息,因此這些字段、方法符號引用不經過運行期轉換的話無法得到真正的內存地址,也就無法直接被虛擬機使用,當虛擬機運行時,需要從常量池中獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中
      常量池中每一項常量都是一張表,目前總共有14種常量,這14種常量類型所代表的具體含義如下表所示:
常量池中數據項類型 類型標誌 類型描述
CONSTANT_Utf8 1 UTF-8編碼的Unicode字符串
CONSTANT_Integer 3 int類型字面值
CONSTANT_Float 4 float類型字面值
CONSTANT_Long 5 long類型字面值
CONSTANT_Double 6 double類型字面值
CONSTANT_Class 7 對一個類或接口的符號引用
CONSTANT_String 8 String類型字面值
CONSTANT_Fieldref 9 對一個字段的符號引用
CONSTANT_Methodref 10 對一個類中聲明的方法的符號引用
CONSTANT_InterfaceMethodref 11 對一個接口中聲明的方法的符號引用
CONSTANT_NameAndType 12 對一個字段或方法的部分符號引用

      常量池中的14種常量項的結構總表如下:

訪問標誌

      在常量池結束之後緊接着的兩個字節代表訪問標誌(access_flags),這個標誌用於識別一些類或接口層次的訪問信息,包括:這個Class是類還是接口,是否定義爲public;是否定位爲abstract類型,如果是類的話是否被聲明爲final等。具體的標誌位以及標誌的含義如下表所見:

      

類索引、父類索引與接口所有集合

      類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,而接口索引集合石一組u2類型的數據的集合,Class文件中由這三項數據來確定這個類的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名,由於Java語言不允許多重繼承,所以父類索引只有一個,除了Object之外,所有的類都有父類。
      類索引、父類索引、接口集合都按順序排列在訪問標誌之後,類索引和父類索引用兩個u2類型的索引值表示,他們各自指向一個類型爲CONSTANT_CLASS_info的類描述符常量。

字段表集合

      字段表(field_info)用於描述接口或者類中聲明的變量。字段(field)包括類級別變量以及實例級別變量,但不包括在方法內部聲明的局部變量。我盟可以想象一下在Java中描述一個字段可以包含什麼信息?可以包含的信息有:字段的作用域、是實例變量還是類變量,可否被序列化,字段數據類型等。

方法表集合

      如果理解了上面的字段表集合的內容,那麼理解其方法表集合就會很簡單,Class文件存儲格式中對方法的描述與對字段的描述幾乎採用了完全一致的方式,方法表的結構如同字段表一樣,包括了訪問標誌、名稱索引、描述符索引、屬性表集合幾項。

字節碼指令

      Java虛擬機的指令是由一個字節長度、代表着某種特定操作含義的數字(稱爲操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需參數而構成,由於Java虛擬機採用面向操作數棧而不是寄存器的架構,所以大多數的指令都不包含操作數,只有一個操作碼。

字節碼與數據類型

       在Java虛擬機的指令集中,大多數的指令都包含了其操作所對應的數據類型信息。例如iload指令用於從局部變量表中加載int類型的數據到操作數棧中,而fload用於加載float類型的數據了。對於大部分與數據類型相關的字節碼指令,他們的操作碼助記符中都有特殊的字符來表明專門爲哪種數據類型服務:ibiaoshiduiint類型的數據操作,l代表long。s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。

加載和存儲指令

      加載和存儲指令用於將數據在棧幀中的局部變量表和操作數棧之間來回傳輸,這類指令包括以下內容:
    (1)將一個局部變量加載到操作數棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。
    (2)將一個數值從操作數棧存儲到局部變量表:istore、istore_<n>、lstore、lstore<n>。。。。
    (3)將一個常量加載到操作數棧:bipush、sipush、ldc、ldc_w等。
    (4)拓充局部變量表的訪問索引的指令:wide。

      存儲數據的操作數棧和局部變量表主要就是由加載和存儲指令進行操作,初次之外還有少量指令,如訪問對象的字段或數組元素的指令也會想操作數棧傳輸數據

運算指令

      運算或算數指令用於對兩個操作數棧上的值進行某種特定的運算,並把結果重新存入到操作數棧頂。大體運算指令可以分爲兩種:堆整型數據進行運算的指令與對浮點型數據進行運算的指令,無論是哪種算數指令,都使用Java虛擬機的數據類型,由於沒有直接支持byte、short、char、boolean類型的算數指令,對於這些類型數據的運算,應使用操作int類型的指令代替。所有的算數指令如下"
    (1)加法指令:iadd、ladd、fadd、dadd
    (2)減法指令:isub、lsub、fsub、dsub
    (3)乘法指令:imul、lmul、fmul、dmul
    (4)除法指令:idiv、ldiv、fdiv、ddiv
    (5)求餘指令:irem、lrem、frem、drem
    (6)取反指令:ineg、lneg、fneg、dneg
    (7)位移指令:ishl、ishr、iushr、lshl、lshr、lushr
    (8)按位或指令:ior、lor
    (9)按位與指令:iand、land
    (10)局部變量自增指令:iinc
    (11)比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

對象創建與訪問指令

     雖然類實力和數組都是對象,但Java虛擬機對類實力和數組的創建與操作使用了不同的字節碼指令。對象的創建指令如下:
   (1)創建類實力的指令:new
   (2)創建數組的指令:newarray、anewarray、multianewarray
   (3)訪問類字段和實例字段:getfield、putfield、getstatic、putstatic。
   (4)把一個數組元素加載到操作數棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
   (5)講一個操作數棧的值存儲到數組中的指令:bastotr、castore、sastore、iastore、fastore、dastore、aastore
   (6)取數組長度的指令:arraylenght
   (7)檢查類實力的指令:instanceof、checkcast

操作數棧管理指令

     如同操作一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用於直接操作操作數棧的指令。
   (1)將操作數棧的站定一個或兩個元素出棧:pop、pop2
   (2)複製站定一個或兩個數值並將複製或雙份的複製值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
   (3)將棧最頂端的兩個數值互換:swap

控制轉移指令

     控制轉移之類可以讓Java虛擬機有條件或無條件的從指定的位置指令而不是控制轉移之類的下一條指令繼續執行程序,從概念模型上理解,可以認爲控制轉移指令就是在有條件或無條件地修改PC寄存器的值:
   (1)條件分支:ifeq、iflt、ifle、ifne、ifge、ifnull、ifnonull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_campge和if_acmpne
   (2)符合條件分支:tableswitch、lookupswitch
   (3)無條件分支:goto、goto_w、jsr、jsr_w、ret
     Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操作,

方法調用和返回指令

   (1)invokevirtual:用於調用對象的實例方法,根據對象的實際類型進行分派(調用),這也是Java語言中最常見的犯法分派方式。
   (2)invokeeinterface:用於調用接口方法,他會在運行時搜索一個實現了這個接口方法的對象,找出合適的方法進行調用。
   (3)invokeespecial:用於調用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。
   (4)invokestatic:用於調用static方法。
   (5)invokedynamic:用於在運行時動態解析出調用點限定符所引用的方法,並執行方法。
    方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturtn和arrturn。
    除了上述一些指令外,還有異常處理指令、同步指令,這裏就不再多說。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章