訪問標識(access_flag)
- 在常量池后,緊跟著訪問標記,標記使用兩個字節表示,用于識別一些類或接口層次的訪問信息,包括這個Class是類還是接口,是否定義為public類型,是否定義為abstract類型,如果是類的話,是否聲明為final等,各種訪問標記如下:
- 類的訪問權限通常為ACC_開頭的常量
- 每一種類型的表示都是通過設置訪問標記的32位中的特定位來實現的,比如,若是public final的類,則該標記為ACC_PUBLIC | ACC_FINAL
- 使用ACC_SUPER可以讓類更準確地定位到父類的方法super.method(),現代編譯器都會設置并且使用這個標記
- 帶有ACC_INTERFACE標志的class文件表示是接口不是類,反之則表示是類而不是接口
- 如果一個class文件被設置了ACC_INTERFACE標志,那么同時也得設置ACC_ABSTRACT標志,同時不能設置ACC_FINAL、ACC_SUPER或ACC_ENUM標志
- 如果沒有設置ACC_INTERFACE標志,那么這個class文件可以具有上表中除ACC_ANNOTATION外的其他所有標志,當然ACC_FINAL和ACC_ABSTRACT這類互斥的標志除外,這兩個標志不能同時設置
- ACC_SUPER標志用于標志用于確定類或接口里面的invokespecial指令使用的哪一種執行語義,針對Java虛擬機指令集的編譯器都應設置這個標志,對于Java SE8及后續版本來說,無論class文件中這個標志的實際值是什么,也不管class文件的版本是多少,Java虛擬機會為每個class文件均設置ACC_SUPER標志
- ACC_SUPER標志為了向后兼容由舊Java編譯器所編譯的代碼而設計,目前的ACC_SUPER標志在由JDK1.0.2之前的編譯器生成的access_flags中沒有確定意義,如果設置該標志,oracle的JVM實現會將其忽略
- ACC_SYNTHETIC標志意味著該類或接口是由編譯器生成的,而不是由源代碼生成的
- 注解類型必須設置為ACC_ANNOTATION標志,如果設置了ACC_ANNOTATION標志,那么也必須設置ACC_INTERFACE標志
- ACC_ENUM標志表明該類或其父類為枚舉類型
- 表中沒有使用的Access_flags標志為未來擴充預留,這些預留標志在編譯器中應該設置為0,JVM實現也應忽略他們
類索引、父類索引、接口索引集合
- 在訪問標記后,會指定該類的類別、父類類別及實現的接口,格式如下
- 這三項數據來確認這個類的繼承關系
- 類索引用于確定這個類的全限定名
- 類索引用于確定這個類的父類的全限定名,由于Java語言不允許多重繼承,父類索引只有一個,除java.lang.Object除外,所有的Java類都有父類,除java.lang.Object外,所有Java類的父類索引都不為0
- 接口索引集合就用來描述這個類實現了哪些接口,這些被實現的接口按implements語句(如果類本身為接口,則應使用extends語句)后的接口順序從左到右排列在接口索引集體中
類索引this_class
2字節無符號整數,指向常量池的索引,它提供了類的全限定名,如com/chapter09/Demo,this_class的值必須是對常量池表中某項的一個有效索引,常量池在這個索引處的成員都必須為CONSTANT_Class_info類型結構體,該結構表示這個class文件所定義的類或接口
父類索引super_class
- 2字節無符號整數,指向常量池的索引,它提供了父類的全限定名,如無繼承關系,其默認繼承java/lang/Object類,由于Java不支持多繼承,其父類只有一個
- superclass指向的父類不能是final
interfaces
- 指向常量也索引集合,它提供了一個符號引用到所有已實現的接口
- 由于一個類可以實現多個接口,因此需要以數組形式保存多個接口的索引,表示接口的第個索引也是一個指向常量池的CONSTANT_Class(此處必須是接口,不是類)
- interfaces_count接口計數器
interfaces_count項的值表示當前類或接口的直接超接口數量 - interfaces[]接口索引集合
interfaces[]中每個成員的值必須是對常量池表中某項的有效索引值,它的長度為interfaces_count,每個成員interfaces[i]必須為CONSTANT_Class_info結構,其中0 <= i < interfaces_count,在interfaces[]中,各成員所表示的接口順序和對應的源代碼中給定的接口順序一樣,即interfaces[0]對應的是源代碼中最左邊的接口
字段表集合
- 用于描述接口或類聲明的變量,字段(field),包括類級變量以及實例級變量,但不包括方法內部、代碼塊內部聲明的局部變量
- 字段叫什么名字、字段定義的是什么數據類型,這此都是無法固定的,只能引用常量池中的常量來描述
- 它指向常量池索引集合,它描述了每個字段的完整信息,比如字段的標識符、訪問修飾符(public,private,protected),是類變量還是實例變量(static修飾符),是否是常量(final修飾符)等
注: - 字段表集體中不會列出從父類或者實現接口中繼承而來的字段,但有可能列出原本Java代碼中不存在的字段,譬如在內部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的字段
- 在Java語言中字段是無法重載的,兩個字段的數據類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對于字節碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的
字段計數器fields_count
- fields_count的值表示當前class文件fields表的成員個數,使用兩個字節來表示
- fields表中線個成員都是一個field_info結構,用于表示該類或接口所聲明的所有類字段或實例字段,不包括方法內部聲明的變量,也不包括從父類或父接口繼承的那些字段
字段表fields[]
- fields表中的每個成員都必須是一個fields_info結構的數據項,用于表示當前類或接口中某個字段的完整描述
- 一個字段的信息包括如下這些信息,這些信息中,各個修飾符都是布爾值,要么有,要么沒有
- 作用域(public,private,protected修飾符)
- 是實例變量還是類變量(static修飾)
- 可變性(final)
- 并發可見性(volatile修飾,是否強制從 內存讀寫)
- 可否序列化(transient修飾)
- 字段數據類型(基本數據類型、對象、數組)
- 字段名稱
- 字段表結構
字段表訪問標識
一個字段可以被各種關鍵字去修飾,如作用域修飾符(public,private,protected)、static修飾符、final修飾、volatile修飾符等,因此,其可像類的訪問標志那樣,使用一些標志來標記字段,字段的訪問標志有如下:
字段名索引
- 根據字段名索引的值,查詢常量池中的指定索引項即可
描述符索引
- 描述符的作用用來描述字段的數據類型、方法的參數列表和返回值,根據描述符規則,基本數據類型及代表無返回值的void類型都用一個大寫字符來表示,而對象用L加對象的全限定名來表示,如下:
屬性表集合
- 一個字段還可能擁有一些屬性,用于存儲更多的額外信息,比如初始化值、一些注釋信息等,屬性個數存放在Attribute_count中,屬性具體內容存放在attributes數組中
//以常量屬性為例
ConstantValue_attribute {u2 attribute_name_index;u4 attribute_length;u2 constantvalue_index;
}
//說明常量屬性而言,attribute_length的值恒為2
方法表集合
屬性表集合
待續… …