《深入理解java虛擬機器》學習筆記5——Java Class類檔案結構

yangxi_001發表於2013-12-04

Java語言從誕生之時就宣稱一次編寫,到處執行的跨平臺特性,其實現原理是原始碼檔案並沒有直接編譯成機器指令,而是編譯成Java虛擬機器可以識別和執行的位元組碼檔案(Class類檔案,*.class),位元組碼檔案是一種平臺無關的中間編譯結果,位元組碼檔案由java虛擬機器讀取,解析和執行,java虛擬機器遮蔽了不同作業系統和硬體平臺的差異性。

如今的java虛擬機器已經稱為一種通用平臺,不但能夠執行java語言,Groovy,JRuby,Jython等一大批動態語言也可以直接在Java虛擬機器上執行,其原理也是這些動態語言的編譯器將原始碼檔案編譯為和Java相同的位元組碼檔案,這樣Java虛擬機器就可以像執行java語言一樣執行這些動態語言了。

位元組碼class類檔案是由一系列位元組碼命令組成,用於表示程式中各種常量、變數、關鍵字和運算子號的語義等等。Java的Class類檔案是一組以8為位元組為單位的二進位制流,各個資料項嚴格按照順序緊湊地排列在Class類檔案之中,中間沒有新增任何分隔符,當遇到需要佔用8位位元組以上空間的資料項時,按照高位在前的方式分割成若干個8位位元組進行儲存。

Java虛擬機器規定,Class類檔案格式採用類似C語言結構體的偽結構來儲存,這種偽結構中只有兩種資料型別:無符號數和表:

(1).無符號數:

屬於基本型別的資料,以u1, u2, u4, u8來分別代表1個位元組,2個位元組,4個位元組和8個位元組的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼的字串值。

(2).表:

由多個無符號數或其他表作為資料項構成的複合資料型別,所以表都習慣性地以“_info“結尾。表用於描述有層次關係的複合結構資料,整個Class檔案本質就是一張表。

Java 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

attributes_count

1

attribute_info

attributes

attributes_count

Class類檔案沒有任何分隔符,是嚴格按照這個結構表順序排列,下面具體介紹各個名稱含義:

(1).magic:

每個Class檔案的頭4個位元組被稱為魔數,它的唯一作用是用於確定這個檔案是否為一個能被java虛擬機器所接收的Class類檔案,即用於判定檔案是否是符合規範的java Class檔案。雖然說字尾名“.class”可以表明檔案是一個Class檔案,但是檔案字尾名是可以隨意被改動的,基於安全的考慮,很多檔案都通過魔數值來唯一確定檔案型別,java的Class檔案魔數是:0xCAFEBABE.

(2).minor_version和major_version:

每個Class檔案的第5和第6個位元組代表Class檔案的次版本號,第7和第8個位元組代表Class檔案的主版本號。

Class檔案的主、次版本號是由JDK決定的,JDK1.0~JDK1.1使用了45.0~45.3的版本號(45是主版本好,點”.“之後的是次版本號),從JDK1.1開始,每個大版本的JDK主版本號加1.

Class主、次版本號的一個作用時,高版本的Java虛擬機器可以向前相容,執行低版本JDK編譯的Class位元組碼檔案,而低版本的java虛擬機器不能執行高版本JDK編譯的Class位元組碼檔案。當低版本的java虛擬機器執行高版本JDK編譯的Class位元組碼檔案時,通常會報類似如下的異常:

[java] view plaincopy
  1. Exception in thread "main" java.lang.UnsupportedClassVersionError: a (Unsupporte  
  2. d major.minor version 49.0)  

JDK1.0~JDK1.1使用了45.0~45.3的版本號,JDK1.2使用了46.0~46.65535的版本號,JDK1.3使用了47.0~47.65535的版本號,JDK1.4使用了48.0~48.65535的版本號,JDK1.5使用了49.0~49.65535的版本號,JDK1.6使用了50.0~50.65535的版本號,JDK1.7使用51.0~51.65535的版本號。

在編譯時可以通過指定-target引數來改變主版本號,如JDK1.6編譯時如果沒有給定target引數,則編譯出來的Class檔案的主版本號是50,如果給定”-target 1.4 -source 1.4”引數之後,則主版本將變為48,如果給定”-target 1.5 ”引數之後,則主版本將變為49。

(3). constant_pool_count和constant_pool:

constant_pool_count代表Class檔案中常量池的數目,由於常量池的計數從1開始,因此常量池的容量是constant_pool_count-1。

第0項常量空出做特殊考慮,為了滿足一些指向常量池的索引值在某些特定情況下需要表達“不指向任何一個常量池”的意思。

constant_pool常量池是Class類檔案中出現的第一個表型別資料,常量池主要存放兩大類常量:

a.字面量(Literal):包括文字字串、final型別常量值。

b.符號引用(SymbolicReferences):包括類和介面的全限定名、欄位的名稱和描述符、方 法的名稱和描述符。

(4). access_flags:

用於表示Class或介面層次的訪問標誌,即類或介面層面的訪問控制資訊,通常儲存的資訊包括:Class類檔案是類、介面、列舉或是註解;是否定義為public型別;是否定義為abstract型別;類是否被定義為final等等。

(5). this_class、super_class和interfaces:

this_class類索引用於確定類的全限定名,super_class父類索引用於確定父類的全限定名,interfaces介面索引用於確定介面的全限定名,由於java中可以實現多個介面,因此使用interfaces_count來儲存介面數量。

(6). field:

field_info欄位表用於描述介面或者類中宣告的變數,field欄位包括了類級變數(靜態變數)和例項級變數(成員變數),但不包括方法內部的區域性變數。

fields_count欄位數目表示Class檔案中的類和例項變數總數,欄位存放的資訊包括:欄位訪問標誌、是否靜態、是否final、是否併發可見volatile、是否可序列化transient、資料型別、欄位名稱等等。

注意:欄位表中不包含從父類或者介面中繼承而來的欄位,但是會新增原本程式碼中不存在的欄位,例如this,以及內部類對外部類訪問而自動新增的外部類例項欄位等。

(7).method:

method_info方法表用於描述類或者介面中宣告的方法,methods_count用於表示Class檔案中方法總數,method方法儲存了方法的訪問標識、是否靜態、是否final、是否同步synchronized、是否本地方法native、是否抽象方法abstract、方法返回值型別、方法名稱、方法引數列表等資訊。

方法的程式碼指令並沒有直接存放在方法表中,而是存放著屬性表中的方法表Code中。

注意:如果父類的方法在子類沒有被重寫,方法表中不會出現來自父類的方法資訊,但是編譯器會自動新增類構造器”<clinit>”方法和例項構造器”<init>”方法。

Java編譯器的方法特徵簽名只包括:方法名稱、引數順序和引數型別,不包括方法返回值型別,因此java的方法過載不能通過方法的返回值類區別,但是在Class檔案中,方法特徵簽名包括方法的返回值型別,因此Class檔案中可以共存兩個名稱和引數完全相同而返回值型別不同的方法。

(8). attribute:

attribute_info屬性表是Class檔案格式中最具擴充套件性的一種資料專案,用於存放field_info欄位表、method_info方法表以及Class檔案的專有資訊,屬性表不要求各個屬性有嚴格順序,只要求不與已有的屬性名字重複即可,屬性表中存放的常用資訊如下:

屬性名稱

使用位置

含義

Code

方法表

Java程式碼編譯後的位元組碼指令

ConstantValue

欄位表

final關鍵字定義的常量值

Deprecated

類、方法表、欄位表

被宣告為Deprecated的欄位或方法

Exception

方法表

方法丟擲的異常

InnerClasses

類檔案

內部類列表

LineNumberTable

Code屬性

java原始碼行號和位元組碼指令的對應關係

LocalVariableTable

Code屬性

方法的區域性變數描述

SourceFile

類檔案

原始檔名稱

Synthetic

類、方法表、欄位表

標識方法或欄位為編譯器自動生成

Class檔案是二進位制檔案,使用支援二進位制的文字編輯器開啟之後顯示的全是二進位制資料,非常的不便於閱讀和理解,使用JDK提供的javap工具可以簡單將Class反編譯,編譯理解Class檔案的結構,例子如下:

原始碼:

[java] view plaincopy
  1. public class Test {    
  2.     public int getNum(int i) {    
  3.         return i + 1;    
  4.     }    
  5. }    

javap反編譯之後的位元組碼檔案:

[java] view plaincopy
  1. public class Test extends java.lang.Object    
  2.   SourceFile: "Test.java"    
  3.   minor version: 0    
  4.   major version: 50    
  5. //常量池  
  6.  Constant pool:    
  7. const #1 = class        #2;       
  8. const #2 = Asciz        Test;    
  9. const #3 = class        #4;       
  10. const #4 = Asciz        java/lang/Object;    
  11. const #5 = Asciz        <init>;  //例項構造器  
  12. const #6 = Asciz        ()V;  //void返回型別  
  13. const #7 = Asciz        Code;  //屬性表Code屬性  
  14. const #8 = Method       #3.#9;  //方法特徵簽名  java/lang/Object."<init>":()V    
  15. const #9 = NameAndType  #5:#6;//  方法名稱和返回值"<init>":()V    
  16. const #10 = Asciz       LineNumberTable;  //屬性表原始碼行號和位元組碼指令對應表  
  17. const #11 = Asciz       LocalVariableTable;  //屬性表方法區域性變數表  
  18. const #12 = Asciz       this;  //Test類例項物件本身  
  19. const #13 = Asciz       LTest;;  //物件型別,Test類  
  20. const #14 = Asciz       getNum;  //方法名稱  
  21. const #15 = Asciz       (I)I;  //方法引數列表為一個int型別和返回值為int型別  
  22. const #16 = Asciz       i;  //引數名稱i  
  23. const #17 = Asciz       I;  //引數型別int  
  24. const #18 = Asciz       SourceFile;    
  25. const #19 = Asciz       Test.java;    
  26.  //方法表  
  27. {    
  28. //建構函式(預設構造方法)  
  29. public Test();    
  30.   Code:  //屬性表Code屬性  
  31.    Stack=1, Locals=1, Args_size=1    
  32.    0:   aload_0    
  33.    1:   invokespecial   #8//Method java/lang/Object."<init>":()V    
  34.    4:   return    
  35.   LineNumberTable:    
  36.    line 20    
  37.     
  38.   LocalVariableTable:  //屬性表方法區域性變數表  
  39.    Start  Length  Slot  Name   Signature    
  40.    0      5      0    this       LTest;    
  41.     
  42. //自定義方法  
  43. public int getNum(int);    
  44.   Code:    
  45.    Stack=2, Locals=2, Args_size=2    
  46.    0:   iload_1    
  47.    1:   iconst_1    
  48.    2:   iadd    
  49.    3:   ireturn    
  50.   LineNumberTable:    
  51.    line 40    
  52.     
  53.   LocalVariableTable:    
  54.    Start  Length  Slot  Name   Signature    
  55.    0      4      0    this       LTest;    
  56.    0      4      1    i       I    
  57. }    

相關文章