隨筆-3  評論-0  文章-0  trackbacks-0

          class文件之常量池分析


          當(dāng)JVM運行Java程序的時候,它會加載對應(yīng)的class文件,并提取class文件中的信息存放在JVM開辟出來的方法區(qū) 內(nèi)存中。那么這個class文件里面到底有些什么內(nèi)容呢?

           

          一、class文件內(nèi)容概述

           

          class文件是由8bits的字節(jié)流組成,全部字節(jié)構(gòu)成了15個有意義的項目。這些項目之間沒有任何無意義的字節(jié),因此class文件非常緊湊。占據(jù)多字節(jié)空間的項目按照高位在前的順序存放。下面我們詳細(xì)討論這些項目:

           

          ★ magic(魔數(shù))    每個class文件的前4個字節(jié)稱為魔數(shù),值為0xCAFEBABE。作用在于輕松的辨別class文件與非class文件。

           

          ★ minor_version、major_version(次、主版本號)   各占2個字節(jié)。隨著Java技術(shù)的發(fā)展,class文件的格式會發(fā)生變化。版本號的作用在于使得虛擬機(jī)能夠認(rèn)識當(dāng)前加載class的文件格式。從而準(zhǔn)確的提取class文件信息。

           

          ★ constant_pool_count 、constance_pool(常量池)  從這里開始的字節(jié)組成了常量池  存儲了諸如符號常量、final常量值、基本數(shù)據(jù)類型的字面值等內(nèi)容。JVM會將每一個常量構(gòu)成一個常量表,每個常量表都有自己的入口地址。而實際上在JVM會將這些常量表存儲在方法區(qū)中一塊連續(xù)的內(nèi)存空間中,因此class文件會根據(jù)常量表在常量池中的位置對其進(jìn)行索引。比如常量池中的第一個常量表的索引值就是1,第二個就是2。有的時候常量表A需要常量表B的內(nèi)容,則在常量表A中會存儲常量表B的索引值x。而constant_pool_count就記錄了有多少個常量表,或則所有多少個索引值。實際上,常量池中沒有索引值為0的常量表,但這缺失的索引值也被記錄在constant_pool_count中,因此 constant_pool_count等于常量表的數(shù)量加1。關(guān)于常量池的具體內(nèi)容,我們會在下面詳細(xì)講述,并用一個例子來顯示整個class文件的內(nèi)容。

           

          ★ access_flags(訪問標(biāo)志)   占用2個字節(jié)。用來表明該class文件中定義的是類還是接口,訪問修飾符是public還是缺省。類或接口是否是抽象的。類是否是final的。

           

          ★ this_class     占用2個字節(jié)。  它是一個對常量池的索引。指向的是常量池中存儲類名符號引用的CONSTANT_Class_info常量表(見下面常量池具體結(jié)構(gòu))。比如this_class=0x0001。則表示指向常量池中的第一個常量表。通常這個表是指向當(dāng)前class文件所定義的類名。

           

          ★ super_class  占用2個字節(jié)  與this_class類似,指向存放當(dāng)前class文件所定義類的超類名字的索引的 CONSTANT_Class_info常量表。

           

          ★ inteface_count、interfaces  interface_count是class文件所定義的類直接實現(xiàn)的接口或父類實現(xiàn)的接口的數(shù)量。占2個字節(jié)。intefaces包含了對每個接口的 CONSTANT_Class_info常量表的索引。 

           

          ★fields_count、fields   fields_count表明了類中字段的數(shù)量 。fields是不同長度的field_info表的序列。這些field_info表中并不包含超類或父接口繼承而來的字段。field_info表展示了一個字段的信息,包括字段的名字,描述符和修飾符。如果該字段是final的,那么還會展示其常量值。注意,這些信息有些存放在field_info里面,有些則存放在field_info所指向的常量池中。下面我們闡述一下這個field_info表的格式:

                      access_flags(2byte 訪問修飾符)  

                      name_index(2byte 存儲字段名的常量表在常量池中的索引)

                      description_index(2byte 存儲字段的所屬類型的常量表在常量池中的索引)

                      attribute_count(2byte 屬性表的數(shù)量)

                      attribute (屬性)

          其中attribute是由多個attribute_info組成。而JVM規(guī)范定義了字段的三種屬性:ConstanceValue、Deprecated和Synthetic。

           

          ★method_count、methods 與字段類似,method_count表明類中方法的數(shù)量和每個方法的常量表的索引。methods表明了不同長度的method_info表的序列。該表格式如下:

                      access_flags(2byte 訪問修飾符)  

                      name_index(2byte 存儲方法名的常量表在常量池中的索引)

                      description_index(2byte 存儲方法的返回類型和參數(shù)類型的常量表在常量池中的索引)

                      attribute_count(2byte 屬性表的數(shù)量)

                      attribute (屬性)

          其中方法的屬性JVM規(guī)定了四種:Code,Deprecated,Exceptions,Synthetic。

           

           

          二、常量池的具體結(jié)構(gòu)

          在Java程序中,有很多的東西是永恒的,不會在運行過程中變化。比如一個類的名字,一個類字段的名字/所屬類型,一個類方法的名字/返回類型/參數(shù)名與所屬類型,一個常量,還有在程序中出現(xiàn)的大量的字面值。比如下面小段源碼紅色顯示的東西。

          public class ClassTest {

                  private String itemS ="我們 ";

                  private final int itemI =100 ;

                  public void setItemS (String para ){...}

          }

          而這些在JVM解釋執(zhí)行程序的時候是非常重要的。那么編譯器將源程序編譯成class文件后,會用一部分字節(jié)分類存儲這些永恒不變的紅色東西。而這些字節(jié)我們就成為常量池。事實上,只有JVM加載class后,在方法區(qū)中為它們開辟了空間才更像一個“池”。

           

          正如上面所示,一個程序中有很多永恒的紅色東西。每一個都是常量池中的一個常量表(常量項)。而這些常量表之間又有不同,class文件共有11種常量表,如下所示:

          常量表類型 標(biāo)志值(占1 byte) 描述
          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 對一個字段或方法的部分符號引用

          (1) CONSTANT_Utf8   用UTF-8編碼方式來表示程序中所有的重要常量字符串。這些字符串包括: ①類或接口的全限定名, ②超類的全限定名,③父接口的全限定名, ④類字段名和所屬類型名,⑤類方法名和返回類型名、以及參數(shù)名和所屬類型名。⑥字符串字面值

                表格式:   tag(標(biāo)志1:占1byte)       length(字符串所占字節(jié)的長度,占2byte)      bytes(字符串字節(jié)序列)

           

          (2) CONSTANT_Integer、 CONSTANT_Float、 CONSTANT_Long、 CONSTANT_Double  所有基本數(shù)據(jù)類型的字面值。比如在程序中出現(xiàn)的1用CONSTANT_Integer表示。3.1415926F用 CONSTANT_Float表示。 

                表格式:   tag             bytes(基本數(shù)據(jù)類型所需使用的字節(jié)序列)

           

          (3) CONSTANT_Class  使用符號引用來表示類或接口。我們知道所有類名都以 CONSTANT_Utf8表的形式存儲。但是我們并不知道CONSTANT_Utf8表中哪些字符串是類名,那些是方法名。因此我們必須用一個指向類名字符串的符號引用常量來表明。

               表格式:   tag    name_index(給出表示類或接口名的CONSTANT_Utf8表的索引)

           

          (4) CONSTANT_String  同 CONSTANT_Class,指向包含字符串字面值的 CONSTANT_Utf8表。

              表格式:   tag    string_index(給出表示字符串字面值的CONSTANT_Utf8表的索引)

           

          (5) CONSTANT_Fieldref  CONSTANT_Methodref、 CONSTANT_InterfaceMethodref     指向包含該字段或方法所屬類名的CONSTANT_Utf8表,以及指向包含該字段或方法的名字和描述符的 CONSTANT_NameAndType 

              表格式:   tag   class _index(給出包含所屬類名的CONSTANT_Utf8表的索引)  name_and_type_index(包含字段名或方法名以及描述符的 CONSTANT_NameAndType表 的索引)

           

          (6) CONSTANT_NameAndType  指向包含字段名或方法名以及描述符的 CONSTANT_Utf8表。

              表格式:   tag    name_index(給出表示字段名或方法名的CONSTANT_Utf8表的索引)  type_index(給出表示描述符的CONSTANT_Utf8表的索引)

           

          下面是我將一個源程序編譯成class文件后,對文件中的每一個字節(jié)的分析,可以更好的理解class文件的內(nèi)容以及常量池的組成。

           

           

          三、TestClass.class 文件實例分析

          Java代碼 
          1. //源代碼  
          2. package hr.test;  
          3. //ClassTest類  
          4. public class ClassTest {  
          5.     private int itemI=0;  //itemI類字段  
          6.     private static String itemS="我們"//itemS類字段  
          7.     private final float PI=3.1415926F;  //PI類字段  
          8.     //構(gòu)造器方法  
          9.     public ClassTest(){  
          10.     }  
          11.     //getItemI方法  
          12.     public int getItemI(){  
          13.         return this.itemI;  
          14.     }  
          15.     //getItemS方法  
          16.     public static String getItemS(){  
          17.         return itemS;  
          18.     }  
          19.     //main主方法  
          20.     public static void main(String[] args) {  
          21.         ClassTest ct=new ClassTest();  
          22.     }  
          23. }  

           

          TestClass.class 字節(jié)碼分析(字節(jié)順序從上到下,從左到右。每個字節(jié)用一個0-255的十進(jìn)制整數(shù)表示)

           

               202 254 186 190   -- 魔數(shù)                                                                                                  
               0 0     -- 次版本號                                                                                                                                    
               0 50   -- 主版本號                                                                                                                

               0 43   -- 常量池中常量表的數(shù)量有42個,下面紅色括號中的數(shù)據(jù)表明該常量表所在常量池中的索引,從索引1開始

          (1) 7 0 2  -- 對類ClassTest的符號引用(7為標(biāo)志  02指向了常量池的索引2的位置)

          (2) 1 0 17 104 114 47 116 101 115 116 47 67 108 97 115 115 84 101 115 116  -- 類全限定名hr\test\ClassTest 
          (3) 7 0 4    -- 對類Object的符號引用

          (4) 1 0 16 106 97 118 97 47 108 97 110 103 47 79 98 106 101 99 116   -- 超類全限定名 java/lang/Object    
          (5) 1 0 5 105 116 101 109 73    --  第1個類字段名 itemI   
          (6) 1 0 1 73    --  I  第1個類字段類型為整型 
          (7) 1 0 5 105 116 101 109 83    --  第2個類字段名 itemS 
          (8) 1 0 18 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59    --  第2個類字段類型的全限定名 Ljava/lang/String 
          (9) 1 0 2 80 73 -- 第3個類字段名PI 
          (10) 1 0 1 70  -- 第3個類字段類型為float 
          (11) 1 0 13 67 111 110 115 116 97 110 116 86 97 108 117 101    ---  第3個類字段為常量ConstantValue

          (12) 4 64 73 15 218   -- 第3個類字段float字面值,占4bytes(3.1415926) 
          (13) 1 0 8 60 99 108 105 110 105 116 62    -- <clinit>  初始化方法名 
          (14) 1 0 3 40 41 86     -- ()V 方法的返回類型為void

          (15) 1 0 4 67 111 100 101     -- Code 
          (16) 8 0 17 -- String字符串字面值(0 17表示索引1 7) 
          (17) 1 0 6 230 136 145 228 187 172    -- "我們" 
          (18) 9 0 1 0 19  -- 指向 第2個 字段的引用(0 1指向索引1,0 19指向索引19) 
          (19) 12 0 7 0 8   --指向 第2個 字段的名字和描述符的索引, 
          (20) 1 0 15 76 105 110 101 78 117 109 98 101 114 84 97 98 108 101    -- LineNumberTable 
          (21) 0 18 76 111 99 97 108 86 97 114 105 97 98 108 101 84 97 98 108 101    -- LocalVariableTable 
          (22) 1 0 6 60 105 110 105 116 62   -- <init>   表示初始化方法名 
          (23) 10 0 3 0 24 --  指向父類Object的構(gòu)造器方法,0 3表示父類名常量表的索引,0 24表示存放該方法名稱和描述符的引用的常量表的索引 
          (24) 12 0 22 0 14  --  指向方法名和描述符的常量表的索引。0 22是方法名的常量表索引,0 14是描述符的常量表索引 
          (25) 9 0 1 0 26    -- 指向第1個字段的引用, 0 1表示字段所屬類型的索引,0 26表示字段名和描述符的索引 
          (26) 12 0 5 0 6    -- 指向第1個字段的名字和描述符的索引 
          (27) 9 0 1 0 28    -- 指向第3個字段的引用, 0 1表示字段所屬類型的索引,0 28表示字段名和描述符的索引 
          (28) 12 0 9 0 10  -- 指向第3個字段的名字和描述符的索引 
          (29) 1 0 4 116 104 105 115    --  隱含參數(shù)符號this 
          (30) 1 0 11 76 67 108 97 115 115 84 101 115 116 59    --  LClassTest; 
          (31) 1 0 8 103 101 116 73 116 101 109 73    - - 方法名 getItemI 
          (32) 1 0 3 40 41 73    -- ()I  方法描述符:返回類型int 
          (33) 1 0 8 103 101 116 73 116 101 109 83   --  方法名 getItemS 
          (34) 1 0 20 40 41 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59     --- 方法描述符()Ljava/lang/String; 
          (35) 1 0 4 109 97 105 110     --  主方法名main 
          (36) 1 0 22 40 91 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59 41 86    ---  ()Ljava/lang/String;)V  主方法中的參數(shù)的字符串?dāng)?shù)組類型名 
          (37) 10 0 1 0 24    指向當(dāng)前 ClassTest 類的構(gòu)造器方法,0 1表示存放當(dāng)前類名的常量表的索引。0 24是存放方法名和描述符的符號引用的常量表索引。   
          (38) 1 0 4 97 114 103 115  -- 參數(shù)args 
          (39) 1 0 19 91 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59   -- 字符串?dāng)?shù)組 [Ljava/lang/String; 
          (40) 1 0 2 99 116    ---  對象符號ct 
          (41) 1 0 10 83 111 117 114 99 101 70 105 108 101    -- SourceFile 
          (42) 1 0 14 67 108 97 115 115 84 101 115 116 46 106 97 118 97    -- ClassTest.java    

           

                 0 33 ---- access_flag 訪問標(biāo)志  public
                 0 1   ---- this_class  指向當(dāng)前類的符號引用在常量池中的索引
                 0 3  ---- super_class
           
                 0 0 ---- inteface_count接口的數(shù)量 

                 0 3   ---  field_count字段的數(shù)量

                 // 字段 itemI 
                 0 2  ---- private 修飾符
                 0 5  ---- 字段名在常量池中的索引,字段itemI
                 0 6   ---- 字段的描述符(所屬類型)在常量池中的索引
                 0 0   ---  字段的屬性信息表(attribute_info)的數(shù)量
                 
          // 字段 itemS 
                 0 10   ----  private static 修飾符
                 0 7  ---字段名在常量池中的索引,字段itemS
                 0 8  ---字段的描述符(所屬類型)在常量池中的索引
                 0 0  ---  字段的屬性信息表(attribute_info)的數(shù)量
                  
          // 字段 PI   
                 0 18   -- private final 修飾符
                 0 9 ---字段名在常量池中的索引,//字段PI
                 0 10 ---字段的描述符(所屬類型)在常量池中的索引
                 0 1  --- 字段的屬性信息表(attribute_info)的數(shù)量
                 0 11   --- 屬性名在常量池中的索引。即ConstantValue
                 0 0 0 2 --- 屬性所占的字節(jié)長度
                 0 12   --- 屬性值在常量池中的索引。即常量字面值
           

                 0 5  -- Method_count方法的數(shù)量 
                 //類的靜態(tài)數(shù)據(jù)初始化方法<clinit> 
                 0 8 ---- static 修飾符(所有的初始化方法都是static的) 
                 0 13 --- 在常量池中的索引。初始化方法名<clinit>,該方法直接由JVM在特定的時候調(diào)用,并非由字節(jié)碼生成。 
                 0 14 --- 在常量池中的索引。返回類型為void。

                 0 1 --- 屬性數(shù)量 
                 0 15 -- 屬性名 在常量池中的索引。即code 
                 0 0 0 42 ---  屬性所占的字節(jié)長度2 
                 0 1 0 0 0 0 0 6 18 16 179 0 18 177 0 0 0 2 0 20 0 0 0 10 0 2 0 0 0 5 0 5 0 2 0 21 0 0 0 2 0 0 ---該方法的字節(jié)碼指令序列和其他信息
                 //類的普通實例數(shù)據(jù)的初始化方法,針對類構(gòu)造器生成的<init>方法。 
                 0 1 --- public 修飾符 
                 0 22 --- 構(gòu)初始化方法名<init> 
                 0 14 --- 構(gòu)造器的返回類型為void 
                 0 1  --- 屬性數(shù)量 
                 0 15 ---  屬性名在常量池中的索引。即Code 
                 0 0 0 70 -- 屬性所占的字節(jié)長度70 
                 0 2 0 1 0 0 0 16 42 183 0 23 42 3 181 0 25 42 18 12 181 0 27 177 0 0 0 2 0 200 0 0 18 0 4 0 0 0 8 0 4 0 4 0 9 0 6 0 15 0 9 0 21 0 0 0 12 0 10 0 0 16 0 29 0 30 0 0 ---該方法的字節(jié)碼指令序列和其他信息 
                 //getItemI方法 
                 0 1 --- public 修飾符 
                 0 31 ---  在常量池中的索引。方法名getItemI 
                 0 32 ---  在常量池中的索引。方法返回類型為int 
                 0 1 -- 屬性數(shù)量 
                 0 15  --- 屬性名在常量池中的索引。即Code 
                 0 0 0 47 ---  屬性所占的字節(jié)長度70 
                 0 1 0 1 0 0 0 5 42 180 0 25 172 0 0 0 2 0 20 0 0 0 6 0 1 0 0 0 12 0 21 0 0 0 12 0 1 0 0 0 5 0 29 0 30 0 0  --- 該方法的字節(jié)碼指令序列和其他信息 
                 //getItemS方法 
                 0 9 --- public static 修飾符 
                 0 33 ---  在常量池中的索引。方法名getItemS 
                 0 34 - -- 在常量池中的索引。方法返回類型為String 
                 0 1 --- 屬性數(shù)量 
                 0 15 -- 屬性名在常量池中的索引。即Code 
                 0 0 0 36 ---  屬性所占的字節(jié)長度36 
                 0 1 0 0 0 0 0 4 178 0 18 176 0 0 0 2 0 20 0 0 0 6 0 1 0 0 0 16 0 21 0 0 0 2 0 0 --該方法的字節(jié)碼指令序列和其他信息 
                 //main方法 
                 0 9 --- public static 修飾符 
                 0 35 ---  在常量池中的索引。主方法名main 
                 0 36 -- 在常量池中的索引。方法返回類型為String[] 
                 0 1 ---  屬性數(shù)量 
                 0 15  ---  屬性名在常量池中的索引。即Code 
                 0 0 0  65 ---  屬性所占的字節(jié)長度36 
                 0 2 0 2 0 0 0 9 187 0 1 89 183 0 37 76 177 0 0 0 2 0 20 0 0 0 10 0 2 0 0 0 20 0 8 0 21 0 21 0 0 0 22 0 2 0 0 0 9 0 38 0 39 0 0 0 8 0 1 0 40 0 30 0 1 0 1 0 41 0 0 0 2 0 42

           

           

          我們分析上面的字節(jié)碼例子,不難看出:

           

          藍(lán)色背景的常量池字節(jié)碼區(qū)域:

          (1) 所有的字面值都是存放在常量池中的。 特別注意的是“我們”這個字符串常量也是在常量池中的。如果一個程序出現(xiàn)多個“我們”,那么常量池中也只會有一個。另外,也正是因為“我們”存放在常量池中,使得一些字符串的==比較變的需要琢磨了。

          (2)ClassTest并沒有任何顯示的父類。但在常量池中,我們發(fā)現(xiàn)有Object的符號常量存在。 這也證實了在Java中,任何類都直接或間接繼承了Object的,而Object并不需要在代碼中顯示繼承,JVM會幫我們做到這一點。

          (3)常量池中有一個隱含參數(shù)this的符號常量。即使程序中不存在this,JVM也會悄悄的設(shè)置一個這樣的對象。

           

          綠色背景的類字段字節(jié)碼區(qū)域:

          (1)字段PI是浮點型常量,在編譯期的字節(jié)碼中就已經(jīng)指定好了PI的字面值存儲在常量池中的某個索引內(nèi) 。這一點也證實了Java中的常量在編譯期就已經(jīng)得到了值,在運行過程中是無法改變的。

           

          橙色背景的類方法字節(jié)碼區(qū)域:

          (1)主方法main是作為ClassTest的類方法存在的,在字節(jié)碼中main和其他的類方法并沒有什么區(qū)別。 實際上,我們也確實可以通過ClassTest.main(..)來調(diào)用ClassTest中的main方法。

           

          (2)在class文件常量池字節(jié)碼中有兩個比較特別的方法名符號:<clinit>和<init>。其中<clinit>方法是編譯器自己生成的,編譯器會把類靜態(tài)變量的直接初始化語句和靜態(tài)初始化語句塊的代碼都放到了class文件的<clinit>方法中。而對所有非靜態(tài)非常量數(shù)據(jù)域的初始化工作要靠<init>方法來完成。針對每一個類的構(gòu)造方法,編譯器都會產(chǎn)生一個<init>方法。即使是缺省構(gòu)造器也不例外。


          來自:http://hxraid.javaeye.com/blog/687660

          posted on 2010-12-07 17:51 QZZF 閱讀(240) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 九寨沟县| 大化| 八宿县| 旬阳县| 东山县| 拉萨市| 南靖县| 赣榆县| 六枝特区| 梁河县| 遂宁市| 康定县| 大竹县| 老河口市| 双桥区| 红原县| 普格县| 晋江市| 古交市| 芦溪县| 牙克石市| 全州县| 汾阳市| 布拖县| 依安县| 蒲城县| 普兰县| 萍乡市| 纳雍县| 中西区| 曲阳县| 淮安市| 嘉兴市| 博罗县| 当雄县| 石屏县| 华亭县| 大方县| 江陵县| 漳州市| 浮山县|