深入理解Java Class文件格式(八)


在本專欄的第一篇文章 深入理解Java虛擬機到底是什麼 中, 我們主要講解了什麼是虛擬機, 這篇博客是對JVM的一個概述。 在隨後的幾篇文章中,一直在講解class文件格式。 在今天這篇博客中, 將會繼續講解class文件中的其他信息。 在本文中, 將會講解class文件中的最後一部分, 屬性(attributes) 。 這裏的屬性和源文件中的屬性不是一個概念。 在源文件中, 我們把在類中定義的字段也叫做屬性。 而class文件中的屬性, 可以看做是存儲一些額外信息的數據結構。 下面我們就來介紹屬性。


class文件中的attributes_count和attributes


attributes_count位於class文件中methods的下面。 它佔兩個字節, 存儲的是一個整數值, 表示class文件中屬性的個數。 

attributes_count下面的是attributes, 可以把它看做一個數組, 每個數組項是一個attribute_info , 每個attribute_info 表示一個屬性。attributes中有 attributes_count個attribute_info 。

需要說明的是, 屬性會出現在多個地方, 不僅僅出現在頂層的ClassFile中, 也會出現在class文件中的數據項中, 如出現在field_info中, 用來描述特定字段的一些信息, 還可以出現在method_info中, 用來描述特定方法的一些信息。 (關於field_info和method_info已經在上面一篇博客中介紹過, 不明白的可以參考上面的博客: 深入理解Java Class文件格式(七)

屬性(attribute_info)的大概格式是這樣的:


其中attribute_name_index佔兩個字節, 它是一個指向常量池數據項的索引。 它指向一個CONSTANT_Utf8_info , 這個CONSTANT_Utf8_info 中存放的是當前屬性的名字。

attribute_name_index下面的四個字節叫做attribute_length, 它表示當前屬性的長度, 這個長度不包括前6個字節, 也就是說只包括屬性真實信息(也就是info)的長度。

attribute_length下面的數據是info, 它的長度由上面提到的attribute_length指定, 它存放的是真實的屬性數據。



下面我們會依次介紹一些重要屬性, 相對不是很重要的屬性會一筆帶過。


ClassFile中的SourceFile屬性


首先介紹一個比較簡單的屬性:SourceFile。 該屬性出現在頂層的class文件中。 它描述了該類是從哪個源文件中編譯來的, 注意, 描述的是源文件, 而不是類, 一個源文件中可以存在多個類。 它的格式如下:

前面說過, attribute_name_index指向常量池中的一個CONSTANT_Utf8_info , 這個CONSTANT_Utf8_info 中存放的是這個屬性的名字字符串, 即“SourceFile” 。 

attribute_length是屬性信息的長度, 這裏是2, 因爲這個屬性的info就兩個字節。

sourcefile_index佔兩個字節, 這也是爲什麼attribute_length是2的原因。 sourcefile_index指向常量池中的一個CONSTANT_Utf8_info , 這個CONSTANT_Utf8_info 中存放的是生成該類的源文件的文件名, 這裏的文件名不包括路徑部分。

下面舉例說明, 示例代碼:

package com.jg.zhang;

public class Person {

	int age;

	int getAge(){
		return age;
	}
}

反編譯後的相關信息:

public class com.jg.zhang.Person

  SourceFile: "Person.java"

Constant pool:

.........

  #20 = Utf8               SourceFile
  #21 = Utf8               Person.java

.........

反編譯結果中的  SourceFile: "Person.java"  一行是SourceFile屬性的簡單表示形式。 可以把它看做一個可讀的attribute_info 。 下面常量池中的第20項的CONSTANT_Utf8_info是對這個屬性的屬性名(attribute_name_index)的描述 , 第21項的CONSTANT_Utf8_info是對源文件的文件名的描述。

下面是圖例, 注意, 虛線範圍內表示常量池區域:





ClassFile中的InnerClasses屬性


InnerClasses是一個存在於頂層class文件中的屬性, 它描述的是內部類和外圍類的關係。  這是一個相對來說比較複雜的屬性, 因爲每個類可能有多個內部類, 而這些內部類中可能還有內部類, 多層嵌套。外圍類中的InnerClasses屬性必須描述它的所有內部類, 而內部類中的InnerClasses也必須描述它的外圍類。 

由於這個屬性相對較爲複雜, 而對於我們理解class文件又不具有很大的意義, 所以我們只是簡單的介紹一下。 如果想深入理解這個屬性, 請參考 《深入Java虛擬機》 第144到166頁。 

下面是這個屬性的結構:



attribute_name_index和attribute_length就不過多介紹了, 和上面介紹的是一樣的。

number_of_classes描述的是內部類的個數。

classes可以看做是一個數組, 這個數組中的每一項是一個inner_class_info, 而每個inner_class_info是對一個內部類的描述。每個 inner_class_info的結構如下:




Synthetic屬性


Synthetic屬性可以出現在filed_info中, method_info中和頂層的ClassFile中, 分別表示這個字段, 方法或類不是有用戶代碼生成的(即不存在與源文件中), 而是由編譯器自動添加的。 例如, 編譯器會爲內部類增加一個字段, 該字段是對外部類對象的引用; 如果一個不定義構造方法, 那麼編譯器會自動添加一個無參數的構造方法<init>, 如果定義了靜態字段或靜態代碼塊, 還會根據具體情況, 增加靜態初始化方法<clinit> 。 此外, 有些機制, 如動態代理, 會在運行時自動生成字節碼文件, 由於這些類不是由源文件中編譯來的, 所以這些類的class文件中會有一個Synthetic屬性。 

它的結構如下:



可以看到, 它沒有真正的屬性數據info, 它只是一個標誌性的屬性, 用來表示它所在的字段, 方法或類是由編譯器自動添加的 。 

下面以實例代碼來說明, 源碼如下:
package com.jg.zhang;

public class Person {
	
	static{
		System.out.println("static");
		
	}
	
	int age;

	int getAge(){
		return age;
	}
}

反編譯後的相關信息如下:

{
  int age;
    flags:


  static {};

    .........

  public com.jg.zhang.Person();
    
    .........

  int getAge();

    .........
}

由反編譯結果可以看出, 編譯器自動生成了靜態初始化方法和構造方法。 可能是因爲Synthetic屬性是可選的(也就是說某個版本的編譯器可以選擇不加入Synthetic屬性) ,所以在反編譯後的結果中沒有發現Synthetic屬性。
 


ConstantValue屬性


ConstantValue屬性出現在class文件中的field_info中, 也就是說它是一個和字段相關的屬性。 每個field_info中最多隻能出現一個ConstantValue屬性。 此外, 要注意的是, 必須是靜態字段纔可以有ConstantValue屬性。 這個靜態字段可以是final的, 也可以不是final的。 

這個屬性爲靜態變量提供了另一種初始化的方式。 靜態變量初始化的方式有兩種, 一種就是現在要講得ConstantValue屬性, 另一種就是靜態初始化方法<clinit> 不同的編譯器和虛擬機可以有不同的實現方式。 但是如果虛擬機決定使用ConstantValue屬性爲靜態變量賦值, 那麼爲這個變量的賦值動作, 必須位於執行<clinit>方法之前。 

此外, 只有基本數據類型或String類型的靜態變量纔可以存在ConstantValue屬性, 原因在下面會有說明。 

下面介紹它的結構:



attribute_name_index和attribute_length就不過多介紹了, 和上面介紹的是一樣的。這裏的attribute_length爲2 。 

位於attribute_length之下的是constantvalue_index , 這是一個指向常量池中某個數據項的索引。這個常量池數據項中存放的就是當前字段的值。

 這個常量池中的數據項,根據field_info描述的字段的不同, 可以是不同類型的數據項, 如果當前字段是byte, short, char, int, boolean類型, 那麼這個被指向的常量池數據項就會是一個CONSTANT_Integer_info , 如果當前字段是一個long類型的字段, 那麼這個被指向的常量池數據項就會是一個CONSTANT_Long_info 。 如果當前字段是是一個String類型的字段 , 那麼這個被指向的常量池數據項就是一個CONSTANT_String_info 。 這裏有一點需要說明, 雖然java語言支持byte, short, char, boolean類型, 但是JVM卻不支持這幾種類型, 表現在class文件中就是, class文件中的常量池中沒有和這幾個數據類型相對應的數據項, 這幾中類型都被JVM在執行時當做int來對待, 表現在class文件中就是, 這幾種類型都對應常量池中的CONSTANT_Integer_info 數據項。 

這也說明了, 爲什麼只有基本數據類型和String類型的靜態常量纔會存在ConstantValue屬性 。 因爲constantvalue_index只是一個指向常量池的索引, 而其他引用類型的常量不會存在於常量池中。

下面以實例來說明, 實例代碼如下:
package com.jg.zhang;

public class Person {
	
	static final int a = 1;
	
	int age;

	int getAge(){
		return age;
	}
}

反編譯後的相關結果如下:
......

Constant pool:
 
   #7 = Utf8               ConstantValue
   #8 = Integer            1

 
{
  static final int a;
    flags: ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

    .........
}

可以看到, 源文件中的a字段, 是static final 的, 所以編譯器爲這個字段的filed_info生成了ConstantValue屬性。 這個屬性的示意圖如下所示, 注意, 虛線範圍內表示常量池區域:





Deprecated屬性


Deprecated屬性可以存在於filed_info中, method_info中和頂層的ClassFile中, 分別表示這個字段, 方法或類已經過時。 這個屬性用來支持源文件中的@deprecated註解。 也就是說, 如果在源文件中爲一個字段, 方法或類標註了@deprecated註解, 那麼編譯器就會在class文件中爲這個字段, 方法或類生成一個Deprecated屬性 。

Deprecated屬性的格式如下:



和上面的屬性一樣, attribute_name_index屬性指向一個常量池中的CONSTANT_Utf8_info 。 這個CONSTANT_Utf8_info中存放着該屬性的名字 “Deprecated” 。 

attribute_length永遠爲0 , 因爲這個屬性只是一個標誌信息, 用來表示字段, 方法, 類已經過時, 而不具有任何實質性的屬性信息。

下面以代碼示例來說明, 代碼如下:

package com.jg.zhang;

public class Person {
	
	int age;

	@Deprecated
	int getAge(){
		return age;
	}
}

在getAge方法上使用了@deprecated 。 下面是反編譯之後的相關信息:

  ......
  
Constant pool:
  ......

  #18 = Utf8               Deprecated

  ......

{

  ......

  int getAge();
    flags:
    Deprecated: true

    ......

}

可以看到, 在getAge方法相關的信息中, 有一行 Deprecated: true , 這說明編譯器在getAge方法的method_info中加入了Deprecated屬性。 常量池第18項的CONSTANT_Utf8_info中存放的是Deprecated屬性的屬性名“Deprecated” 。 

下面是示意圖, 虛線範圍內表示常量池區域:




總結


本文就到此爲止。 在本文中, 主要講解了class文件中的一些屬性。 這些屬性可以出現在class文件中的對個地方, 用來描述一些其他信息。 

在下一篇博客中, 會繼續講解其他屬性。 下一篇博客要講解的屬性相對比較重要, 因爲這些屬性主要是和方法相關的。 到目前爲止, 我們已經講解了class文件中的大部分信息, 包括常量池, this_class, super_class, field_info, method_info等等。 雖然method_info是對一個方法的描述, 但是目前我們知道的而關於method_info的信息, 只描述了方法的方法名, 描述符等簽名信息。 但是方法中還包括很多重要信息, 比如字節碼指令, 異常處理塊, 方法聲明拋出的異常 等。 這些重要信息在class文件中是如何描述的呢? 下一篇博客將會揭曉答案, 敬請關注。 




更多關於深入理解Java的文章, 請關注我的專欄 : http://blog.csdn.net/column/details/zhangjg-java-blog.html

更多關於Java和Android等其他技術的文章, 請關注我的博客: http://blog.csdn.net/zhangjg_blog



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