學習資料來自:java
https://blog.csdn.net/zhangjg_blog/article/details/21781021#t4c++
深刻理解Java JVM虛擬機數組
https://blog.csdn.net/zhangjg_blog/article/details/21557357併發
Class文件是一組以8位字節數爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件存儲的內容幾乎所有是程序的必要的數據,沒有空隙存在。學習
Class文件相似於C語言結構體的僞結構的存儲數據,這種結構中只有兩種數據類型,無符號數和表。測試
無符號數屬於基本的數據類型,圖下展現了對應的類型介紹:this
無符號數能夠用來描述數字,索引引用,數量值或者按照UTF-8編碼構成的字符串值。編碼
表是由多個無符號數或者其餘表做爲數據項構成的符合數據類型,全部表習慣性以"info"結尾,表用於描述有層次結構的複合結構數據。整個Class文件本質上就是一張表。.net
Class文件結構相似以下所示:翻譯
ClassFile{ magic u4, minor_version u2, major_version u2, constant_pool_count u2, constant_pool cp_info*constant_pool_count, access_flags u2, this_class u2, super_class u2, interface_count u2, interfaces u2 * interface_count, fields_count u2, fields field_info * fields_count, methods_count u2, methods method_info * methods_count, attributes_count u2, attributes attributes_info * attributes_count }
不管是無符號數仍是表,當須要描述同一類型可是數量不定的多個數據的時候,常常會使用一個前置容量計數器加若干個連續的數據項的形式,這時候稱之爲某一類型的集合。
上面列出了文件結構,這裏作個整理。
這裏講下面java文件經過javac編譯爲.class文件:
public class Test { private static int num=3; private static final int finalnum=3; private int check; public Test(){ check=23; } }
class文件對應的字節流碼:
cafe babe 0000 0034 0018 0a00 0500 1309 0004 0014 0900 0400 1507 0016 0700 1701 0003 6e75 6d01 0001 4901 0008 6669 6e61 6c6e 756d 0100 0d43 6f6e 7374 616e 7456 616c 7565 0300 0000 0301 0005 6368 6563 6b01 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0008 3c63 6c69 6e69 743e 0100 0a53 6f75 7263 6546 696c 6501 0009 5465 7374 2e6a 6176 610c 000c 000d 0c00 0b00 070c 0006 0007 0100 0454 6573 7401 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0021 0004 0005 0000 0003 000a 0006 0007 0000 001a 0008 0007 0001 0009 0000 0002 000a 0002 000b 0007 0000 0002 0001 000c 000d 0001 000e 0000 002b 0002 0001 0000 000b 2ab7 0001 2a10 17b5 0002 b100 0000 0100 0f00 0000 0e00 0300 0000 0500 0400 0600 0a00 0700 0800 1000 0d00 0100 0e00 0000 1d00 0100 0000 0000 0506 b300 03b1 0000 0001 000f 0000 0006 0001 0000 0002 0001 0011 0000 0002 0012
注: 1個字節是8位,最多表示0到256 ,而一位16最多隻表示到16,即F表示16,要表示到256,就還須要第二位,因此1個字節佔2個16進制位,一個16進制位佔0.5個字節,即一個字節表示2個十六進制數。
能夠看到第一個字符爲u4類型的magic變量,稱之爲魔數,該變量來代表是不是一個虛擬機能接受的Class文件。從上面的Class文件中能夠看到,4個字節magic是0xcafebabe,咖啡寶貝的意思,哈哈。
minor_version表明Java的次版本號佔兩個字節,major_version表明主版本號也佔兩個字節。在上述Class文件中:
34表明十進制的52,說明Java版本爲1.8,能夠經過java -version
來驗證準確性:
接下來的是常量池的入口,常量池能夠理解爲Class文件中的資源倉庫。因爲常量池的數量是不固定的,因此須要放置u2類型的數據來表示常量池容量的計數值constant_pool_count,即上述Class文件中的0x0018,轉換爲十進制爲24,代表當前常量池中有23項常量,索引範圍爲1-24。須要注意的是constant_pool_count計數開始爲1,而不是通常計算機中的0。
常量池中主要存放着兩大類常量:字面量(Literal)以及符號引用(Symbolic References),字面量接近於Java層中的常量的概念,如文本字符串,申明爲final的常量值等等,而符號引用則屬於編譯原理方面的概念,主要包括下面三類:
寫過jni的對上面的應該就不陌生了,若是須要,能夠查閱這篇文章。
Java在執行javac編譯的時候,不像c和c++同樣有連接的步驟,而是在虛擬機加載Claaa文件時候進行動態的連接。**也就說在Class文件中不會保存各個方法,字段的最終內存信息,音這些字段,方法的符號引用不通過運行期轉化的話是沒法得到真正的內存入口的。**當虛擬機運行的時候,須要從Class文件的常量池得到對應的符號引用,而後在類建立或者運行時解析,翻譯到具體的內存地址當中。
在常量池中,每一項常量都是一個表,JDK1.7以前有11種不一樣的表結構,在1.7時新增了三種結構,分別是CONSTANT_MethodHandle_info,CONSTANT_MethodType_info,CONSTANT_InvokeDynamic_info。 這14中標有一個共同點,即在表的開始的第一位是一個u1類型的標誌位(記爲tag),表明當前這個常量屬於哪一種常量類型。具體的常量類型以下所示:
從上面的Class字節碼中能夠知道第一項常量的標誌位是0x0a,轉換成十進制爲10,查閱可知10是一種類中方法的符號引用類型CONSTANT_Methodref_info,CONSTANT_Methodref_info表示普通(非接口)方法符號引用,具體的結構以下所示:
CONSTANT_Methodref_info { u1 tag; u2 class_index; //CONSTANT_Class_info u2 name_and_type_index; //CONSTANT_NameAndType_info }
能夠看到u1爲tag標誌位即十進制的10,下面的class_index表示它指向一個CONSTANT_Class_info的數據項, 這個數據項表示被引用的方法所在的類型。class_index如下的兩個字節是一個叫作name_and_type_index的索引, 它指向一個CONSTANT_NameAndType_info,這個CONSTANT_NameAndType_info描述的是被字段或者方法的部分符號的引用。
CONSTANT_Methodref_info這個符號引用包括兩部分, 一部分是該方法所在的類, 另外一部分是該方法的方法名和描述符。 這就是所謂的 「對方法的符號引用」。
這裏若是一直經過Class的字節碼去看就太費勁了,咱們能夠javap來代替對應的查看操做,調用javap -p Test
得到常量池的信息,截圖以下:
上圖中Constant pool表示Class文件中的常量池,圖中總共23個常量值,跟咱們上面經過字節碼計算的方式得出的結果一致。就很清晰了,咱們也不用去一個字節一個字節算出結果,還容易算錯。能夠看到第一個CONSTANT_Methodref_info對應的是init方法,來自於Obejct類,返回值爲void,參數爲空。
14種常量項的結構總表以下:
在常量池結束以後,緊接着的兩個字節表示訪問標誌,這個標誌用於識別類或者接口層次的訪問信息,包括:
標誌位以及含義以下表所示:
這個咱們也能夠經過javap命令來看到對應的標誌:
能夠看到Test類的flag爲ACC_PUBLIC和ACC_SUPER。
類索引(this_class)以及父類索引(super_class)都是一個u2類型的數據,接口索引集合(interfaces)是一組u2類型的數據集合,在Class文件中,經過上述三個數據來肯定這個類的繼承關係。
類索引肯定這個類的全限定名,福類索引肯定這個類的父類的全限定名,接口索引集合用來描述該類實現了哪些接口
類索引,父類索引集合都按順序排列在訪問標誌以後,兩種索引都用一個u2類型的索引值標識,各自指向了一個類型爲CONSTANT_Utf8_info類型的常量中的全限定名字符串。
對飲接口索引集合,入口第一項爲u2類型的接口計數器,記錄接口索引表的容量。若是沒實現任何接口,則爲0,後面接口的索引表不佔用任何字節。
這裏個Test.java,使用javap來反編譯查看對應的相關索引:
//TestInters爲空接口 public class Test implements TestInters { private static int num=3; private static final int finalnum=3; private int check; public Test(){ check=23; } }
反編譯以下:
4Test,即類自己,5爲繼承的類,這裏爲Object類,6爲接口TestInters,爲了驗證,在從新測試以下:
//繼承跟接口 public class Test extends Father implements TestInters,TestInter2,TestInters1 { private static int num=3; private static final int finalnum=3; private int check; public Test(){ check=23; } }
結果以下:
上述結果說明驗證正確。
字段表用於描述接口或者類中聲明的變量。字段包括類級變量以及實例級變量,**包括在方法內部聲明的局部變量。**一個字段能夠包含的信息以下:
各個修飾符均可以用布爾值來設定是否存在,對於字段的數據類型以及名稱,則只能引用常量池中的常量來描述了。字段表的結構以下所示:
字段修飾符在access_flags之中。與類中的access_flags類型相似,都是一個u2類型的數據,標誌位含義以下所示:
隨後的字段是name_index和descriptor_index,都是對常量池的引用,分別表明字段的簡單名稱以及字段和方法的描述符。
簡單名稱是就是咱們在申明變量時候寫的名稱,如:
int kk=0;//kk就是字段的簡單名稱 void add(); //add就是方法的簡單名稱
方法和字段的描述符也很好理解,尤爲是寫過jni的同窗就更簡單了,定義一個com.lin.Test.java文件
public class Test{ private byte add(int[] a,String b,boolean c,float d){ .... } } add方法經過描述符來描述就是: ([ILjava/lang/String;ZF)B
這裏就不詳細說了,能夠查閱Java描述符自行學習。 後面的attributes_count表明着該字段屬性的計數器,若是沒有額外的屬性則爲0,若是有,則在類型爲attribute_info(即屬性表)的attributes中會有對應的數據。屬性表在後面進行學習整理。
Class文件存儲格式中對方法的描述與對字段的描述幾乎是同樣的:
由於volatile以及transient關鍵字不能修飾方法,因此方法表的訪問標誌中沒有ACC_VOLATILE和ACC_TRANSIENT標誌。可是可使用synchronized,native,strictfp和abstract關鍵字修飾方法,則對應增長了訪問標誌:
方法裏的Java代碼會通過編譯器編譯成字節碼指令後,村房子方法屬性表集合中一個名爲"Code"的屬性裏。
若是父類方法在子類中沒有被重寫,那麼方法表集合中就不會出現父類的方法信息,可是有可能出現由編譯器自動添加的方法,好比<init>和<clinit>方法。
對於每一個屬性,他的名字須要從常量池中引用一個CONSTANT_Utf8_info的常量來表示,而屬性值的結構徹底是自定義的,只要經過一個u4的長度屬性說明屬性所佔用的位數便可:
Java中的方法體的代碼通過javac編譯處理後,最終會變成字節碼指令存儲在Code屬性中。Code屬性出如今方法表的屬性集合之中,但並不是全部方法都存在Code屬性,如接口以及抽象類中的抽象方法就不存在。Code結構以下所示:
try catch finally處理機制
。ConstantValue屬性的做用是通知虛擬機自動爲靜態變量賦值。只有被static關鍵字修飾的變量(類變量)才能使用這項屬性,若是在java代碼中聲明以下代碼:
int x=123; static int k=123;
兩隻變量賦值的方式是有差別的,對於非static變量的賦值在實例構造器<init>中進行;對於類變量的賦值則由兩種方式,在類構造器<clinit>中或者使用ConstantValue屬性,具體的實現由虛擬機決定。
ConstantValue屬性只能適用於基本類型和String。
InnerCalsses屬性用於記錄內部類和宿主類之間的關聯。
Deprecated和Synthetic屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別。
Deprecated標識某個類,字段或者方法,已經被程序做者推薦不使用了,即在代碼中經過@Deprecated註解設置。
Synthetic屬性標識該字段或者方法不禁Java源碼直接產生,而是由編譯器自行添加的。全部由非用戶代碼產生的類,方法,字段都應至少設置Synthetic屬性和ACC_SYNTHETIC標誌中的一項。 可是<init>方法和<clinit>方法例外。
JDK1.5以後增長到Class文件規範當中,是一個可選的定長屬性,能夠出現於類,字段表和方法表中,任何類,接口,初始化方法或者成員的泛型簽名若是包含了類型變量或者參數化類型,則Synthetic屬性會爲他記錄泛型簽名信息。
因爲Java語言泛型採用的是僞擦除法實現的僞泛型,在字節碼中,泛型信息編譯以後統統被擦掉,使用擦除法好處在於實現簡單,可是壞處也有,就是沒法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,如運行期作反射處理沒法得到泛型信息。Signature屬性則彌補了這個缺陷。
主要用於保存invokeddynamic指令引用的引導方法限定符。