JVM 之 (10)Class文件結構

Class文件是一組以8位字節爲基礎單位的二進制流,包含多個數據項目(數據項目的順序,佔用的字節數均由規範定義),各個數據項目嚴格按照順序緊湊的排列在Class文件中,不包含任何分隔符,使得整個Class文件中存儲的內容幾乎所有都是程序運行的必要數據,沒有空隙。當遇到須要佔用超過8位字節以上空間的數據項目時,會按照高位在前的方式分割爲多個8位字節進行存儲java

數據項目分爲2種基本數據類型(以及由這兩種基本數據類型組成的集合):數組

1,無符號數,以u一、u二、u四、u8分別表明1個字節、2個字節、4個字節、8個字節的無符號數安全

2,表,以「_info」結尾,由多個無符號數或其它表構成的複合數據類型數據結構

Class文件格式以下表所示:併發

類型ide

名稱工具

數量測試

u4this

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



因爲不包含任何分隔符,故表中的數據項,不管是數量仍是順序,都是被嚴格限定的。哪一個字節表明什麼含義,長度是多少,前後順序如何,都不容許改變

使用下面的測試類進行詳細說明:

[java]  view plain  copy
  1. package com.test;  
  2.   
  3. public class Test {  
  4.     private int m;  
  5.       
  6.     public int getM(){  
  7.         return m + 1;  
  8.     }  
  9. }  

編譯完成的類文件以下:

一,魔數(magic)

每一個Class文件的頭四個字節稱爲魔數,它的惟一做用是用來肯定該文件是否爲一個能被虛擬機接受的Class文件。使用魔數而不使用文件擴展名是出於安全方面的考慮,由於文件擴展名能夠很隨意的被改動

magic:魔數,0xCAFEBABE(cafe babe)

二,Class文件版本(minor_version 和 major_version)

minor_version:佔2字節,次版本號,0x0000

majro_version:佔2字節,主版本號,0x0031,轉化爲十進制爲49,是使用JDK1.5編譯的(JDK1.5:0x0031,JDK1.6:0x0032,JDK1.7:0x0033)

高版本的JDK能夠向下兼容之前版本的Class文件,可是沒法運行之後版本的Class文件,即便文件格式並未發生變化

若是使用JDK1.5運行使用JDK1.6編譯的Class文件,會報:

[plain]  view plain  copy
  1. java.lang.UnsupportedClassVersionError: Bad version number in .class file  

就是因爲JDK1.6編譯的文件版本號超過了JDK1.5虛擬機所接受的範圍

三,常量池(constant_pool_count 和 constant_pool)

因爲常量池中常量的數目不是固定的,因此在常量池入口首先使用一個2字節長的無符號數constatn_pool_count來表明常量池計數值

constant_pool_count:佔2字節,0x0016,轉化爲十進制爲22,即說明常量池中有21個常量(只有常量池的計數是從1開始的,其它集合類型均從0開始),索引值爲1~22。第0項常量具備特殊意義,若是某些指向常量池索引值的數據在特定狀況下須要表達「不引用任何一個常量池項目」的含義,這種狀況能夠將索引值置爲0來表示

constant_pool:表類型數據集合,即常量池中每一項常量都是一個表,共有11種結構各不相同的表結構數據。這11種表都有一個共同的特色,即均由一個u1類型的標誌位開始,能夠經過這個標誌位來判斷這個常量屬於哪一種常量類型,常量類型及其數據結構以下表所示:

類型

簡介

項目

類型

描述

CONSTANT_Utf8_info

utf-8縮略編碼字符串

tag

u1

值爲1

length

u2

utf-8縮略編碼字符串佔用字節數

bytes

u1

長度爲length的utf-8縮略編碼字符串

CONSTANT_Integer_info

整形字面量

tag

u1

值爲3

bytes

u4

按照高位在前儲存的int值

CONSTANT_Float_info

浮點型字面量

tag

u1

值爲4

bytes

u4

按照高位在前儲存的float值

CONSTANT_Long_info

長整型字面量

tag

u1

值爲5

bytes

u8

按照高位在前儲存的long值

CONSTANT_Double_info

雙精度浮點型字面量

tag

u1

值爲6

bytes

u8

按照高位在前儲存的double值

CONSTANT_Class_info

類或接口的符號引用

tag

u1

值爲7

index

u2

指向全限定名(參見備註一)常量項的索引

CONSTANT_String_info

字符串類型字面量

tag

u1

值爲8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

字段的符號引用

tag

u1

值爲9

index

u2

指向聲明字段的類或接口描述符CONSTANT_Class_info的索引項

index

u2

指向字段描述符CONSTANT_NameAndType_info的索引項

CONSTANT_Methodref_info

類中方法的符號引用

tag

u1

值爲10

index

u2

指向聲明方法的類描述符CONSTANT_Class_info的索引項

index

u2

指向名稱及類型描述符CONSTANT_NameAndType_info的索引項

CONSTANT_InterfaceMethodref_info

接口中方法的符號引用

tag

u1

值爲11

index

u2

指向聲明方法的接口描述符CONSTANT_Class_info的索引項

index

u2

指向名稱及類型描述符CONSTANT_NameAndType_info的索引項

CONSTANT_NameAndType_info

字段或方法的部分符號引用

tag

u1

值爲12

index

u2

指向該字段或方法名稱常量項的索引

index

u2

指向該字段或方法描述符常量項的索引

 

首先來看常量池中的第一項常量,其標誌位爲0x07,是一個CONSTANT_Class_info類型常量,此類型常量表明一個類或接口的符號引用。根據其數據結構,接下來2位字節用來保存一個索引值,它指向常量池中一個CONSTANT_Utf8_info類型的常量,此常量表明瞭這個類或接口的全限定名,索引值爲0x0002,即指向了常量池中的第二項常量。

第二項常量標誌位爲0x01,確實是一個CONSTANT_Utf8_info類型的常量。根據其數據結構,接下來2個字節用來保存utf-8縮略編碼字符串長度,其值爲0x000D,轉化爲十進制爲13,即接下來的13個字節爲一個utf-8縮略編碼的字符串,爲com/test/Test,能夠看到正好是測試類的全限定名

因爲Class文件中,類的全限定名、字段、方法都是使用CONSTANT_Utf8_info類型常量來描述名稱,而該常量的長度由2個字節表示,因此類的全限定名、字段名、方法名的最大長度不能超過2個字節所能表示的最大整數,也就是65535

逐個分析是比較麻煩的,可使用JDK自帶的用於分析Class文件字節碼的工具javap(在JDK的bin目錄下):

[plain]  view plain  copy
  1. d:\>javap -verbose Test  
  2. Compiled from "Test.java"  
  3. public class com.test.Test extends java.lang.Object  
  4.   SourceFile: "Test.java"  
  5.   minor version: 0  
  6.   major version: 49  
  7.   Constant pool:  
  8. const #1 = class        #2;     //  com/test/Test   
  9. const #2 = Asciz        com/test/Test;  
  10. const #3 = class        #4;     //  java/lang/Object   
  11. const #4 = Asciz        java/lang/Object;  
  12. const #5 = Asciz        m;  
  13. const #6 = Asciz        I;  
  14. const #7 = Asciz        <init>;  
  15. const #8 = Asciz        ()V;  
  16. const #9 = Asciz        Code;  
  17. const #10 = Method      #3.#11; //  java/lang/Object."<init>":()V  
  18. const #11 = NameAndType #7:#8;//  "<init>":()V  
  19. const #12 = Asciz       LineNumberTable;  
  20. const #13 = Asciz       LocalVariableTable;  
  21. const #14 = Asciz       this;  
  22. const #15 = Asciz       Lcom/test/Test;;  
  23. const #16 = Asciz       getM;  
  24. const #17 = Asciz       ()I;  
  25. const #18 = Field       #1.#19; //  com/test/Test.m:I  
  26. const #19 = NameAndType #5:#6;//  m:I  
  27. const #20 = Asciz       SourceFile;  
  28. const #21 = Asciz       Test.java;  
  29.   
  30. {  
  31. public com.test.Test();  
  32. ......  

省略了顯示結果的後半部分,這裏能夠看到總共有21個常量,而且能夠看到常量的類型,若是常量中保存的爲索引值(#),也會提示索引指向常量的具體內容(//後的內容),固然其中也包含了不少特殊的符號(如:()V),這些將會在後面的「六,字段表集合」與「七,方法表集合」中進行說明

四,訪問標誌(access_flags)

接下來2個字節表明訪問標誌位。這個標誌用於識別類或接口層次的訪問信息,如:這個Class是類仍是接口,是否認義爲public類型,是否認義爲abstract類型等

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

是否爲public類型

ACC_FINAL

0x0010

是否被聲明爲final,只有類可設置

ACC_SUPER

0x0020

是否容許使用invokespecial字節碼指令,JDK1.2之後編譯出來的類這個標誌爲真

ACC_INTERFACE

0x0200

標識這是一個接口

ACC_ABSTRACT

0x0400

是否爲abstract類型,對於接口和抽象類,此標誌爲真,其它類爲假

ACC_SYNTHETIC

0x1000

標識別這個類並不是由用戶代碼產生

ACC_ANNOTATION

0x2000

標識這是一個註解

ACC_ENUM

0x4000

標識這是一個枚舉

根據上面的表格,測試類的訪問標誌爲ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020 =1 | 32 = [00000000][00000001] | [00000000][00010000] = [00000000][00010001] = 33 = 0x0021

五,類索引、父類索引與接口索引集合(this_class 、 super_class 、 interfaces_count 和 interfaces)

Class文件中由這3項數據來肯定這個類的繼承關係

this_class:類索引,用於肯定這個類的全限定名,佔2字節

super_class:父類索引,用於肯定這個類父類的全限定名(Java語言不容許多重繼承,故父類索引只有一個。除了java.lang.Object類以外全部類都有父類,故除了java.lang.Object類以外,全部類該字段值都不爲0),佔2字節

interfaces_count:接口索引計數器,佔2字節。若是該類沒有實現任何接口,則該計數器值爲0,而且後面的接口的索引集合將不佔用任何字節,

interfaces:接口索引集合,一組u2類型數據的集合。用來描述這個類實現了哪些接口,這些被實現的接口將按implements語句(若是該類自己爲接口,則爲extends語句)後的接口順序從左至右排列在接口的索引集合中

this_class、super_class與interfaces中保存的索引值均指向常量池中一個CONSTANT_Class_info類型的常量,經過這個常量中保存的索引值能夠找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串

this_class的值爲0x0001,即常量池中第一個常量,super_class的值爲0x0003,即常量池中的第三個常量,interfaces_counts的值爲0x0000,故接口索引集合大小爲0

[plain]  view plain  copy
  1. const #1 = class        #2;     //  com/test/Test   
  2. const #2 = Asciz        com/test/Test;  
  3. const #3 = class        #4;     //  java/lang/Object   
  4. const #4 = Asciz        java/lang/Object;  

能夠看到測試類的全限定名爲"com/test/Test;",測試類的父類的全限定名爲"java/lang/Object;"

六,字段表集合(fields_count 和 fields)

fields_count:字段表計數器,即字段表集合中的字段表數據個數。佔2字節,其值爲0x0001,即只有一個字段表數據,也就是測試類中只包含一個變量(不算方法內部變量)

fields:字段表集合,一組字段表類型數據的集合。字段表用於描述接口或類中聲明的變量,包括類級別(static)和實例級別變量,不包括在方法內部聲明的變量

在Java中通常經過以下幾項描述一個字段:字段做用域(public、protected、private修飾符)、是類級別變量仍是實例級別變量(static修飾符)、可變性(final修飾符)、併發可見性(volatile修飾符)、可序列化與否(transient修飾符)、字段數據類型(基本類型、對象、數組)以及字段名稱。在字段表中,變量修飾符使用標誌位表示,字段數據類型和字段名稱則引用常量池中常量表示,字段表格式以下表所示:

類型

名稱

數量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

字段修飾符放在access_flags中,佔2字節,其值爲0x0002,可見這個字段由private修飾,與訪問標誌位十分類似

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

字段是否爲public

ACC_PRIVATE

0x0002

字段是否爲private

ACC_PROTECTED

0x0004

字段是否爲protected

ACC_STATIC

0x0008

字段是否爲static

ACC_FINAL

0x0010

字段是否爲final

ACC_VOLATILE

0x0040

字段是否爲volatile

ACC_TRANSIENT

0x0080

字段是否爲transient

ACC_SYNTHETIC

0x1000

字段是否爲編譯器自動產生

ACC_ENUM

0x4000

字段是否爲enum

固然實際上,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED這3個標誌只能選擇一個,接口中的字段必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL標誌,Class文件對此並沒有規定,這些都是java語言所要求的

name_index表明字段的簡單名稱(參見備註二),佔2字節,是一個對常量池的引用 ,其值爲0x0005,即常量池中第5個常量

descriptor_index表明表明參數的描述符(參見備註三),佔2個字節,是一個對常量池的引用,其值爲0x0006,即常量池中第6個常量

[plain]  view plain  copy
  1. const #5 = Asciz        m;  
  2. const #6 = Asciz        I;  

綜上,能夠推斷出源代碼定義的字段爲(和測試類徹底同樣):

[plain]  view plain  copy
  1. private int m;  

字段表包含的固定數據項到descriptor_index結束,以後跟隨一個屬性表集合用於存儲一些附加信息:attributes_count(屬性計數器,佔2字節,0x0000,因此該字段沒有額外須要描述的信息)和attributes(屬性表集合,詳細說明見後面「八,屬性表集合」)

字段表集合中不會列出從父類或父接口中繼承的字段,可是可能列出本來Java代碼之中不存在的字段,如:內部類爲了保持對外部類的訪問性,自動添加指向外部類實例的字段

Java語言中字段是不能重載的,2個字段不管數據類型、修飾符是否相同,都不能使用相同的名稱;可是對於字節碼,只要字段描述符不一樣,字段重名就是合法的

七,方法表集合(methods_count 和 methods)

methods_count:方法表計數器,即方法表集合中的方法表數據個數。佔2字節,其值爲0x0002,即測試類中有2個方法

methods:方法表集合,一組方法表類型數據的集合。方法表結構和字段表結構同樣:

類型

名稱

數量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

數據項的含義很是類似,僅在訪問標誌位和屬性表集合中的可選項上有略微不一樣

因爲ACC_VOLATILE標誌和ACC_TRANSIENT標誌不能修飾方法,因此access_flags中不包含這兩項,同時增長ACC_SYNCHRONIZED標誌、ACC_NATIVE標誌、ACC_STRICTFP標誌和ACC_ABSTRACT標誌

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

字段是否爲public

ACC_PRIVATE

0x0002

字段是否爲private

ACC_PROTECTED

0x0004

字段是否爲protected

ACC_STATIC

0x0008

字段是否爲static

ACC_FINAL

0x0010

字段是否爲final

ACC_SYNCHRONIZED

0x0020

字段是否爲synchronized

ACC_BRIDGE

0x0040

方法是不是由編譯器產生的橋接方法

ACC_VARARGS

0x0080

方法是否接受不定參數

ACC_NATIVE

0x0100

字段是否爲native

ACC_ABSTRACT

0x0400

字段是否爲abstract

ACC_STRICTFP

0x0800

字段是否爲strictfp

ACC_SYNTHETIC

0x1000

字段是否爲編譯器自動產生

 

第一個方法(由編譯器自動添加的默認構造方法):

access_flags爲0x0001,即public;name_index爲0x0007,即常量池中第7個常量;descriptor_index爲0x0008,即常量池中第8個常量

[plain]  view plain  copy
  1. const #7 = Asciz        <init>;  
  2. const #8 = Asciz        ()V;  

接下來2個字節爲屬性計數器,其值爲0x0001,說明這個方法的屬性表集合中有一個屬性(詳細說明見後面「八,屬性表集合」),屬性名稱爲接下來2位0x0009,指向常量池中第9個常量:Code。接下來4位爲0x0000002F,表示Code屬性值的字節長度爲47。接下來2位爲0x0001,表示該方法的操做數棧的深度最大值爲1。接下來2位依然爲0x0001,表示該方法的局部變量佔用空間爲1。接下來4位爲0x0000005,則緊接着的5個字節0x2AB7000AB1爲該方法編譯後生成的字節碼指令(各字節對應的指令不介紹了,可查詢虛擬機字節碼指令表)。接下來2個字節爲0x0000,說明Code屬性異常表集合爲空。

接下來2個字節爲0x0002,說明Code屬性帶有2個屬性,那麼接下來2位0x000C即爲Code屬性第一個屬性的屬性名稱,指向常量池中第12個常量:LineNumberTable。接下來4位爲0x00000006,表示LineNumberTable屬性值所佔字節長度爲6。接下來2位爲0x0001,即該line_number_table中只有一個line_number_info表,start_pc爲0x0000,line_number爲0x0003,LineNumberTable屬性結束。

接下來2位0x000D爲Code屬性第二個屬性的屬性名,指向常量池中第13個常量:LocalVariableTable。該屬性值所佔的字節長度爲0x0000000C=12。接下來2位爲0x0001,說明local_variable_table中只有一個local_variable_info表,按照local_variable_info表結構,start_pc爲0x0000,length爲0x0005,name_index爲0x000E,指向常量池中第14個常量:this,descriptor_index爲0x000F,指向常量池中第15個常量:Lcom/test/Test;,index爲0x0000。第一個方法結束

第二個方法:

access_flags爲0x0001,即public;name_index爲0x0010,即常量池中第16個常量;descriptor_index爲0x0011,即常量池中第17個常量

[plain]  view plain  copy
  1. const #16 = Asciz       getM;  
  2. const #17 = Asciz       ()I;  

接下來2個字節爲屬性計數器,其值爲0x0001,說明這個方法有一個方法屬性,屬性名稱爲接下來2位0x0009,指向常量池中第9個常量:Code。接下來4位爲0x00000031,表示Code屬性值的字節長度爲49。接下來2位爲0x0002,表示該方法的操做數棧的深度最大值爲2。接下來2位爲0x0001,表示該方法的局部變量佔用空間爲1。接下來4位爲0x0000007,則緊接着的7個字節0x2AB400120460AC爲該方法編譯後生成的字節碼指令。接下來2個字節爲0x0000,說明Code屬性異常表集合爲空。

接下來2個字節爲0x0002,說明Code屬性帶有2個屬性,那麼接下來2位0x000C即爲Code屬性第一個屬性的屬性名稱,指向常量池中第12個常量:LineNumberTable。接下來4位爲0x00000006,表示LineNumberTable屬性值所佔字節長度爲6。接下來2位爲0x0001,即該line_number_table中只有一個line_number_info表,start_pc爲0x0000,line_number爲0x0007,LineNumberTable屬性結束。

和第一個方法的LocalVariableTable屬性基本相同,惟一的區別是局部變量this的做用範圍覆蓋的長度爲7而不是5,第二個方法結束

若是子類沒有重寫父類的方法,方法表集合中就不會出現父類方法的信息;有可能會出現由編譯器自動添加的方法(如:<init>,實例類構造器)

在Java語言中,重載一個方法除了要求和原方法擁有相同的簡單名稱外,還要求必須擁有一個與原方法不一樣的特徵簽名(方法參數集合),因爲特徵簽名不包含返回值,故Java語言中不能僅僅依靠返回值的不一樣對一個已有的方法重載;可是在Class文件格式中,特徵簽名即爲方法描述符,只要是描述符不徹底相同的2個方法也能夠合法共存,即2個除了返回值不一樣以外徹底相同的方法在Class文件中也能夠合法共存

javap工具在後半部分會列出分析完成的方法(能夠看到和咱們的分析結果是同樣的):

[plain]  view plain  copy
  1. d:\>javap -verbose Test  
  2. ......  
  3. {  
  4. public com.test.Test();  
  5.   Code:  
  6.    Stack=1, Locals=1, Args_size=1  
  7.    0:   aload_0  
  8.    1:   invokespecial   #10; //Method java/lang/Object."<init>":()V  
  9.    4:   return  
  10.   LineNumberTable:  
  11.    line 3: 0  
  12.   
  13.   LocalVariableTable:  
  14.    Start  Length  Slot  Name   Signature  
  15.    0      5      0    this       Lcom/test/Test;  
  16.   
  17. public int getM();  
  18.   Code:  
  19.    Stack=2, Locals=1, Args_size=1  
  20.    0:   aload_0  
  21.    1:   getfield        #18; //Field m:I  
  22.    4:   iconst_1  
  23.    5:   iadd  
  24.    6:   ireturn  
  25.   LineNumberTable:  
  26.    line 7: 0  
  27.   
  28.   LocalVariableTable:  
  29.    Start  Length  Slot  Name   Signature  
  30.    0      7      0    this       Lcom/test/Test;  
  31. }  

 

八,屬性表集合(attributes_count 和 attributes)

在Class文件、屬性表、方法表中均可以包含本身的屬性表集合,用於描述某些場景的專有信息

與Class文件中其它數據項對長度、順序、格式的嚴格要求不一樣,屬性表集合不要求其中包含的屬性表具備嚴格的順序,而且只要屬性的名稱不與已有的屬性名稱重複,任何人實現的編譯器能夠向屬性表中寫入本身定義的屬性信息。虛擬機在運行時會忽略不能識別的屬性,爲了能正確解析Class文件,虛擬機規範中預約義了虛擬機實現必須可以識別的9項屬性:

屬性名稱

使用位置

含義

Code

方法表

Java代碼編譯成的字節碼指令

ConstantValue

字段表

final關鍵字定義的常量值

Deprecated

類文件、字段表、方法表

被聲明爲deprecated的方法和字段

Exceptions

方法表

方法拋出的異常

InnerClasses

類文件

內部類列表

LineNumberTale

Code屬性

Java源碼的行號與字節碼指令的對應關係

LocalVariableTable

Code屬性

方法的局部變量描述

SourceFile

類文件

源文件名稱

Synthetic

類文件、方法表、字段表

標識方法或字段是由編譯器自動生成的

每種屬性均有各自的表結構。這9種表結構有一個共同的特色,即均由一個u2類型的屬性名稱開始,能夠經過這個屬性名稱來判段屬性的類型

Code屬性:Java程序方法體中的代碼通過Javac編譯器處理後,最終變爲字節碼指令存儲在Code屬性中。固然不是全部的方法都必須有這個屬性(接口中的方法或抽象方法就不存在Code屬性),Code屬性表結構以下:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

max_stack

1

u2

max_locals

1

u4

code_length

1

u1

code

code_length

u2

exception_table_length

1

exception_info

exception_table

exception_table_length

u2

attributes_count

1

attribute_info

attributes

attributes_count

max_stack:操做數棧深度最大值,在方法執行的任什麼時候刻,操做數棧深度都不會超過這個值。虛擬機運行時根據這個值來分配棧幀的操做數棧深度

max_locals:局部變量表所需存儲空間,單位爲Slot(參見備註四)。並非全部局部變量佔用的Slot之和,當一個局部變量的生命週期結束後,其所佔用的Slot將分配給其它依然存活的局部變量使用,按此方式計算出方法運行時局部變量表所需的存儲空間

code_length和code:用來存放Java源程序編譯後生成的字節碼指令。code_length表明字節碼長度,code是用於存儲字節碼指令的一系列字節流。

每個指令是一個u1類型的單字節,當虛擬機讀到code中的一個字節碼(一個字節能表示256種指令,Java虛擬機規範定義了其中約200個編碼對應的指令),就能夠判斷出該字節碼錶明的指令,指令後面是否帶有參數,參數該如何解釋,雖然code_length佔4個字節,可是Java虛擬機規範中限制一個方法不能超過65535條字節碼指令,若是超過,Javac將拒絕編譯

ConstantValue屬性:通知虛擬機自動爲靜態變量賦值,只有被static關鍵字修飾的變量(類變量)纔可使用這項屬性。其結構以下:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

constantvalue_index

1

能夠看出ConstantValue屬性是一個定長屬性,其中attribute_length的值固定爲0x00000002,constantvalue_index爲一常量池字面量類型常量索引(Class文件格式的常量類型中只有與基本類型和字符串類型相對應的字面量常量,因此ConstantValue屬性只支持基本類型和字符串類型)

對非static類型變量(實例變量,如:int a = 123;)的賦值是在實例構造器<init>方法中進行的

對類變量(如:static int a = 123;)的賦值有2種選擇,在類構造器<clinit>方法中或使用ConstantValue屬性。當前Javac編譯器的選擇是:若是變量同時被static和final修飾(虛擬機規範只要求有ConstantValue屬性的字段必須設置ACC_STATIC標誌,對final關鍵字的要求是Javac編譯器本身加入的要求),而且該變量的數據類型爲基本類型或字符串類型,就生成ConstantValue屬性進行初始化;不然在類構造器<clinit>方法中進行初始化

Exceptions屬性:列舉出方法中可能拋出的受查異常(即方法描述時throws關鍵字後列出的異常),與Code屬性平級,與Code屬性包含的異常表不一樣,其結構爲:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_exceptions

1

u2

exception_index_table

number_of_exceptions

number_of_exceptions表示可能拋出number_of_exceptions種受查異常

exception_index_table爲異常索引集合,一組u2類型exception_index的集合,每個exception_index爲一個指向常量池中一CONSTANT_Class_info型常量的索引,表明該受查異常的類型

InnerClasses屬性:該屬性用於記錄內部類和宿主類之間的關係。若是一個類中定義了內部類,編譯器將會爲這個類與這個類包含的內部類生成InnerClasses屬性,結構爲:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_classes

1

inner_classes_info

inner_classes

number_of_classes

inner_classes爲內部類表集合,一組內部類表類型數據的集合,number_of_classes即爲集合中內部類表類型數據的個數

每個內部類的信息都由一個inner_classes_info表來描述,inner_classes_info表結構以下:

類型

名稱

數量

u2

inner_class_info_index

1

u2

outer_class_info_index

1

u2

inner_name_index

1

u2

inner_name_access_flags

1

inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info類型常量索引,該CONSTANT_Class_info類型常量指向常量池中CONSTANT_Utf8_info類型常量,分別爲內部類的全限定名和宿主類的全限定名

inner_name_index指向常量池中CONSTANT_Utf8_info類型常量的索引,爲內部類名稱,若是爲匿名內部類,則該值爲0

inner_name_access_flags相似於access_flags,是內部類的訪問標誌

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

內部類是否爲public

ACC_PRIVATE

0x0002

內部類是否爲private

ACC_PROTECTED

0x0004

內部類是否爲protected

ACC_STATIC

0x0008

內部類是否爲static

ACC_FINAL

0x0010

內部類是否爲final

ACC_INTERFACE

0x0020

內部類是否爲一個接口

ACC_ABSTRACT

0x0400

內部類是否爲abstract

ACC_SYNTHETIC

0x1000

內部類是否爲編譯器自動產生

ACC_ANNOTATION

0x4000

內部類是不是一個註解

ACC_ENUM

0x4000

內部類是不是一個枚舉

LineNumberTale屬性:用於描述Java源碼的行號與字節碼行號之間的對應關係,非運行時必需屬性,會默認生成至Class文件中,可使用Javac的-g:none或-g:lines關閉或要求生成該項屬性信息,其結構以下:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_info

line_number_table

line_number_table_length

line_number_table是一組line_number_info類型數據的集合,其所包含的line_number_info類型數據的數量爲line_number_table_length,line_number_info結構以下:

類型

名稱

數量

說明

u2

start_pc

1

字節碼行號

u2

line_number

1

Java源碼行號

不生成該屬性的最大影響是:1,拋出異常時,堆棧將不會顯示出錯的行號;2,調試程序時沒法按照源碼設置斷點

LocalVariableTable屬性:用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關係,非運行時必需屬性,默認不會生成至Class文件中,可使用Javac的-g:none或-g:vars關閉或要求生成該項屬性信息,其結構以下:

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

local_variable_table_length

1

local_variable_info

local_variable_table

local_variable_table_length

local_variable_table是一組local_variable_info類型數據的集合,其所包含的local_variable_info類型數據的數量爲local_variable_table_length,local_variable_info結構以下:

類型

名稱

數量

說明

u2

start_pc

1

局部變量的生命週期開始的字節碼偏移量

u2

length

1

局部變量做用範圍覆蓋的長度

u2

name_index

1

指向常量池中CONSTANT_Utf8_info類型常量的索引,局部變量名稱

u2

descriptor_index

1

指向常量池中CONSTANT_Utf8_info類型常量的索引,局部變量描述符

u2

index

1

局部變量在棧幀局部變量表中Slot的位置,若是這個變量的數據類型爲64位類型(long或double),

它佔用的Slot爲index和index+1這2個位置

start_pc + length即爲該局部變量在字節碼中的做用域範圍

不生成該屬性的最大影響是:1,當其餘人引用這個方法時,全部的參數名稱都將丟失,IDE可能會使用諸如arg0、arg1之類的佔位符代替原有的參數名稱,對代碼運行無影響,會給代碼的編寫帶來不便;2,調試時調試器沒法根據參數名稱從運行上下文中獲取參數值

SourceFile屬性:用於記錄生成這個Class文件的源碼文件名稱,爲可選項,可使用Javac的-g:none或-g:source關閉或要求生成該項屬性信息,其結構以下:

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

u2

sourcefile_index

1

能夠看出SourceFile屬性是一個定長屬性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info類型常量的索引,常量的值爲源碼文件的文件名

對大多數文件,類名和文件名是一致的,少數特殊類除外(如:內部類),此時若是不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯誤代碼所屬的文件名

Deprecated屬性和Synthetic屬性:這兩個屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念

Deprecated屬性表示某個類、字段或方法已經被程序做者定爲再也不推薦使用,可在代碼中使用@Deprecated註解進行設置

Synthetic屬性表示該字段或方法不是由Java源碼直接產生的,而是由編譯器自行添加的(固然也可設置訪問標誌中的ACC_SYNTHETIC標誌,全部由非用戶代碼產生的類、方法和字段都應當至少設置Synthetic屬性和ACC_SYNTHETIC標誌位中的一項,惟一的例外是實例構造器<init>和類構造器<clinit>方法)

這兩項屬性的結構爲(固然attribute_length的值必須爲0x00000000):

類型

名稱

數量

u2

attribute_name_index

1

u4

attribute_length

1

起始2位爲0x0001,說明有一個類屬性。接下來2位爲屬性的名稱,0x0014,指向常量池中第20個常量:SourceFile。接下來4位爲0x00000002,說明屬性體長度爲2字節。最後2個字節爲0x0014,指向常量池中第21個常量:Test.java,即這個Class文件的源碼文件名爲Test.java

PS:

1,全限定名:將類全名中的「.」替換爲「/」,爲了保證多個連續的全限定名之間不產生混淆,在最後加上「;」表示全限定名結束。例如:"com.test.Test"類的全限定名爲"com/test/Test;"

2,簡單名稱:沒有類型和參數修飾的方法或字段名稱。例如:"public void add(int a,int b){...}"該方法的簡單名稱爲"add","int a = 123;"該字段的簡單名稱爲"a"

3,描述符:描述字段的數據類型、方法的參數列表(包括數量、類型和順序)和返回值。根據描述符規則,基本數據類型和表明無返回值的void類型都用一個大寫字符表示,而對象類型則用字符L加對象全限定名錶示

標識字符

含義

B

基本類型byte

C

基本類型char

D

基本類型double

F

基本類型float

I

基本類型int

J

基本類型long

S

基本類型short

Z

基本類型boolean

V

特殊類型void

L

對象類型,如:Ljava/lang/Object;

對於數組類型,每一維將使用一個前置的「[」字符來描述,如:"int[]"將被記錄爲"[I","String[][]"將被記錄爲"[[Ljava/lang/String;"

用描述符描述方法時,按照先參數列表,後返回值的順序描述,參數列表按照參數的嚴格順序放在一組"()"以內,如:方法"String getAll(int id,String name)"的描述符爲"(I,Ljava/lang/String;)Ljava/lang/String;"

4,Slot,虛擬機爲局部變量分配內存所使用的最小單位,長度不超過32位的數據類型佔用1個Slot,64位的數據類型(long和double)佔用2個Slot

相關文章
相關標籤/搜索