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

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

class文件中的attributes_count和attributes數組

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

attributes_count下面的是attributes, 能夠把它看作一個數組, 每一個數組項是一個attribute_info , 每一個attribute_info 表示一個屬性。attributes中有 attributes_count個attribute_info 。this

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

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

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

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

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

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


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文件中是如何描述的呢? 下一篇博客將會揭曉答案, 敬請關注。

相關文章
相關標籤/搜索