Java虛擬機-類文件結構

類文件結構

Class類文件的結構

任何一個Class文件都對應着唯一一個類或者接口的定義信息,但是類或者接口並不一定都要定義在文件裏(例如類也可以通過類加載器直接生成)。Class文件是一組以8位字節爲基礎單位的二進制流,各項數據項目嚴格按照順序緊湊地排列在Class文件中。Class文件格式採用類似C語言結構體的僞結構來存儲數據,包括兩種數據類型:無符號數和表。
無符號數屬於基本的數據類型,以u1,u2,u4,u8來代表1,2,4,8個字節的無符號數。可以用來描述數字、索引引用、數量值或者按照utf-8編碼構成字符串值。
表是由多個無符號數或者其他表作爲數據項構成的複合數據類型,所有表都習慣性的以“_info”結尾。表用於描述有層次關係的符合結構數據。

魔數與Class文件的版本

Class文件的頭4個字節稱之爲魔數(Magic Number),用於確認這個文件是否爲一個能被虛擬機接受的Class文件。Class文件的魔數值爲:0xCAFEBABE(咖啡寶貝?),這個魔數在Java還是“Oak”的時候就被確認了。
之後4個字節存儲的是Class文件的版本號,第5第6個字節是次版本號(Minor Version),第7第8是主版本號(Major Version)。Java的版本號是從45開始的,1.1之後大版本發佈主版本號向上加1,高版本JDK能向下兼容但不能運行以後版本的Class文件。

常量池

主次版本號之後時常量池,Class文件之中的資源倉庫,關聯其他項目最多的數據類型,也是佔用Class文件空間最大的項目之一。由於常量數量不固定,所以常量池的入口需要防止一項u2類型數據,代表常量池容量計數值(constant_pool_count)。
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近與Java語言層面的常量概念,如文本字符串、聲明爲final的常量值等。符號引用則屬於編譯原理方面的概念,包括下面三類常量:

  • 類和接口的全限定名(Fully Qualified Name)
  • 字段的名稱和描述符(Descriptor)
  • 方法的名稱與描述符

Java代碼在Javac編譯的時候,不像C或C++有連接這一步驟,而是在虛擬機加載Class文件的時候進行動態連接。虛擬機運行時,從常量池獲得對應的符號引用,在類創建時或運行時解析、翻譯到具體的內存地址之中。

訪問標誌

常量池之後緊接着兩個字節代表訪問標誌(access_flag),用於識別一些類或者接口層次的訪問信息。包括:是類還是接口,是否public,是否abstract,是否final等。

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 是否爲public
ACC_FINAL 0x0010 是否爲final
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令的新語意,由於該語意在JDK1.0.2發生過改變,所以之後編譯出來的類這個標誌必須爲真
ACC_INTERFACE 0x0200 是否爲接口
ACC_ABSTRACT 0x0400 是否爲抽象類
ACC_SYNTHETIC 0x1000 是否由編譯器自動產生的
ACC_ANNOTATION 0x2000 是否爲註解類
ACC_ENUM 0x4000 是否爲enum

例如public的普通類,所以ACC_PUBLIC ACC_SUPE標誌爲真,其他標誌爲假。因此access_flags的值爲: 0x0001|0x0020=0x0021
前5個是Java虛擬機規範定義標誌,1.5之後增加了後面的三種。

類索引、父類索引和接口索引集合

類索引(this_class)父類索引(super_class)是u2類型數據,接口索引集合(interfaces)時一組u2類型的數據集合。按照順序排在訪問標誌之後。

類型 名稱 數量
u2 類索引 1
u2 父類索引 1
u2[n] 接口索引 n

字段表集合

字段表(field_info)用於描述接口或者類中聲明的變量。字段包括類級變量以及實例級別變量,但不包括方法內部聲明的局部變量。修飾符使用標誌量表示,字段名稱類型使用常量池中的常量來描述。
access_flag的含義

標誌名稱 標誌值 含義
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
類型 名稱 數量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

方法表集合

方法表(method_info)用於描述接口或者類中聲明的方法。
因爲volatile關鍵字和transient關鍵字不能修飾方法,所以方法表的訪問標誌中沒有了 ACC_VOLATILE標誌和ACC_TRANSIENT標誌。與之相對的,synchronized、native、strictfp 和abstract關鍵字可以修飾方法,所以方法表的訪問標誌中增加了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 是否由編譯器自動產生的
類型 名稱 數量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

屬性表集合

屬性表(attribute_info)之前反覆出現,在Class文件、字段表、方法表都可以攜帶字節的屬性表集合,用於描述某些場景專有信息。
順序、長度、內容的要求不像前面那麼嚴格。
不要求各個屬性表具有嚴格順序,並且只要不與已有屬性名重複,任何人 實現的編譯器都可以向屬性表中寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉它不 認識的屬性。

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節碼指令
ConstantValue 字段表 final關鍵字定義的常量值
Deprecated 類、方法表、字段表 聲明爲deprecated的方法和字段
Exceptions 方法表 方法拋出的異常
EnclosingMethod 類文件 僅當一個類爲局部類或者匿名類纔有這個屬性,用於標識這個類所在的外圍方法

...

屬性表結構:

類型 名稱 數量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

完整結構描述

表用於描述有層次關係的符合結構的數據,整個Class文件本質上就是一張表,其構成成分就是如下的數據項:

數據類型 名稱 數量
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 attribute_count 1
attribute_info attributes attribute_count

實例

爲了能夠更好的理解,我們拿一個實際的類文件來分析一下。

源碼

package com.software5000.base.jsql;

public class TestClass {
    public String strField;

    public int intField;

    public String getStrField() {
        return strField;
    }

    public void setStrField(String strField) {
        this.strField = strField;
    }

    public int getIntField() {
        return intField;
    }

    public void setIntField(int intField) {
        this.intField = intField;
    }
}

這是一個很簡單的測試類,兩個屬性,以及對應的getter/setter方法。

Class文件

Class文件

分析

針對類文件以及前面的概念對比分析

魔數、Class版本

CAFEBABE - 魔數
00000034 - 版本 52 :jdk 1.8

常量池長度

0020 - 32-1 長度31個常量項

常量池內容

01——
0A tag表示 constant_methodref_info
0005 index no. 5 contant_class_info
001B index no. 27 constant_nameandtype

02——
09 tag constant_fieldref_info
0004 index no.4 constant_class_info
001C index no.28 constatn_nameandtype

03——
09 tag constant_fieldref_info
0004 index no.4 constant_class_info
001D index no.29 constatn_nameandtype

04——
07 tag constant_class_info
001E index no.30

05——
07 tag constant_class_info
001F index no.31

06——
01 tag constant_utf8_info
0008 length 8 byte
7374 7246 6965 6C64 strField

07——
01 tag constant_utf8_info
0012 length 18 byte
4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B Ljava/lang/String;

08——
01 tag constant_utf8_info
0008 length 8 byte
696E 7446 6965 6C64 intField

09——
01 tag constant_utf8_info
0001 length 1 byte
49 I

10——
01 tag constant_utf8_info
0006 length 6 byte
3C69 6E69 743E

11——
01 tag constant_utf8_info
0003 length 3 byte
2829 56 ()V

12——
01 tag constant_utf8_info
0004 length 4 byte
436F 6465 Code

13——
01 tag constant_utf8_info
000F length 15 byte
4C69 6E65 4E75 6D62 6572 5461 626C 65 LineNumberTable

14——
01 tag constant_utf8_info
0012 length 18 byte
4C6F 6361 6C56 6172 6961 626C 6554 6162 6C65 LocalVariableTable

15——
01 tag constant_utf8_info
0004 length 4 byte
7468 6973 this

16——
01 tag constant_utf8_info
0026 length 38 byte
4C63 6F6D 2F73 6F66 7477 6172 6535 3030 302F 6261 7365 2F6A 7371 6C2F 5465 7374 436C 6173 733B Lcom/software5000/base/jsql/TestClass;

17——
01 tag constant_utf8_info
000B length 11 byte
67 6574 5374 7246 6965 6C64 getStrField

18——
01 tag constant_utf8_info
0014 length 20 byte
2829 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B ()Ljava/lang/String;

19——
01 tag constant_utf8_info
000B length 11 byte
7365 7453 7472 4669 656C 64 setStrField

20——
01 tag constant_utf8_info
0015 length 21 byte
284C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B29 56 (Ljava/lang/String;)V

21——
01 tag constant_utf8_info
000B length 11 byte
6765 7449 6E74 4669 656C 64 getIntField

22——
01 tag constant_utf8_info
0003 length 3 byte
2829 49 ()I

23——
01 tag constant_utf8_info
000B length 11 byte
7365 7449 6E74 4669 656C 64 setIntField

24——
01 tag constant_utf8_info
0004 length 4 byte
2849 2956 (I)V

25——
01 tag constant_utf8_info
000A length 10 byte
536F 7572 6365 4669 6C65 SourceFile

26——
01 tag constant_utf8_info
000E length 14 byte
5465 7374 436C 6173 732E 6A61 7661 TestClass.java

27——
0C tag constant_Name-andtype-info
000A index no.10
000B index no.11

28——
0C tag constant_Name-andtype-info
0006 index no.6
0007 index no.7

29——
0C tag constant_Name-andtype-info
0008 index no.8
0009 index no.9

30——
01 tag constant_utf8_info
0024 length 36 byte
5636F 6D2F 736F 6674 7761 7265 3530 3030 2F62 6173 652F 6A73 716C 2F54 6573 7443 6C61 7373 com/software5000/base/jsql/TestClass

31——
01 tag constant_utf8_info
0010 length 16 byte
6A61 7661 2F6C 616E 672F 4F62 6A65 6374 java/lang/Object

訪問標誌

0021 訪問標誌 (ACC_PUBLIC,ACC_SUPER兩個標誌位爲真)

類索引、父類索引、接口索引

0004 類索引 no.4
0005 父類索引 no.5
0000 接口索引集合 0

字段表集合

0002 fields_counts 2個字段表數據

No.1 field
0001 access_flag public
0006 index no.6 name_index
0007 index no.7 descriptor_index
0000 attributes_count

No.2 field
0001 access_flag public
0008 index no.8 name_index
0009 index no.9 descriptor_index
0000 attributes_count

方法表集合

0005 methods_count 5個方法

No.1 method
0001 access_flag public
000A index no.10 name_index
000B index no.11 descriptor_index ()V
0001 attributes_count 1個
000C attributes_info LineNumberTable

剩餘的待進一步補充。。。

總結

Class文件的結構理解,說實話對於編碼或者性能優化等方面沒有什麼特別大的幫助,但是能夠幫我們更好的理解Java及其設計的思想。我們從Class文件的結構設計中也能夠學習到一些模式,這些東西可能會在後續的研發過程提供一些解決問題或者設計方案的思路。
這纔是最重要的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章