Class文件結構(五)

在onenote上覆制過來格式全亂了,將就着看吧。。。

1. JVM的平臺無關性和語言無關性

Java實現“一次編寫,處處運行”。是因爲有Java虛擬機夾在操作系統和應用程序之間。

JVM解決了平臺無關性和語言無關性問題

平臺無關性各操作系統提供的系統調用是不一樣的,程序的組織形式也不一樣,例如,Windows系統的可執行程序格式是PE(包括exe,dll,vxd等),而Linux系統下可執行文件是ELF格式(包括.o, .so等)還有老的GCC默認生成的.out格式。而編譯完成的Java程序只要符合標準的class文件規範,即可在JVM上運行。這都是由於JVM屏蔽了不同操作系統的系統調用和程序格式等細節。所以想要實現平臺無關性是需要適配不同的JVM版本的。

語言無關性JVM只認識符合JVM規範(注意與Java語言規範區別)的class文件。至於這個class文件是哪種語言編譯出的是不用管的。如下圖所示:

 

2.Class文件結構

 

class文件全是0/1組成。且以8字節爲基礎單位。這便是其稱爲“字節碼文件”的原因。如果某個數據項有多個字節,則按照大端存儲。

文件中數據類型分爲 無符號數 和 表:

- 無符號數 

它表示class文件中的值,這些值沒有任何類型,但有不同的長度。根據這些值長度的不同分爲:u1、u2、u4、u8,分別代表1字節的無符號數、2字節的無符號數、4字節的無符號數、8字節的無符號數。 可以用來描述數字,引用,字符串等

-  

class文件中所有數據(即無符號數)要麼單獨存在,要麼由多個無符號數組成二維表。即class文件中的數據要麼是單個值,要麼是二維表。

整個class文件中沒有分隔符,沒有空格。所以哪個字節代表什麼含義,長度多少,先後順序如何都要嚴格定義。差錯一個位,其他後面全部錯亂。

 

2.1 class文件的組織結構

1.魔數

2.版本號

3.常量池

4.訪問標誌

5.類索引、父類索引、接口索引集合

6.字段表集合

7.方法表集合

 

  • Class文件的構成1:魔數

位於class文件的頭4個字節,表示這個文件是class文件。

魔數的作用就相當於文件擴展名,但後綴名容易被修改,魔數更安全。

class文件的魔數是用16進製表示的“CAFEBABE”。

  • Class文件的構成2:版本信息

緊接着魔數的4個字節是版本號(次版本號(2字節)+主版本號(2字節))。它表示本class中使用的是哪個版本的JDK。

在高版本的JVM上能夠運行低版本的class文件,但在低版本的JVM上無法運行高版本的class文件,即使該class文件中沒有用到任何高版本JDK的特性也無法運行! 

  • Class文件的構成3:常量池

(1)什麼是常量池?

緊接着版本號之後的就是常量池。可以理解爲class文件的資源倉庫。常量池中存放兩種類型的常量:

  • 字面量
    字面量即Java語言層面的常量概念,如字符串、被final修飾的常量值。
  • 符號引用 
    符號引用就是我們定義的各種名字: 
  1. 類和接口的全限定名
  2. 字段的名字 和 描述符
  3. 方法的名字 和 描述符 

描述符:

 public 、static、final、 abstract、synchronized、volatile等都屬於描述符

 

(2)常量池的特點

  • 常量池長度不固定 
    常量池的大小是不固定的,因此常量池開頭放置一個u2類型的無符號數,用來存儲當前常量池的容量。JVM根據這個值就知道常量池的開始和結束。
    注意:這個值是從1開始的,若爲5表示池中有4個常量。
  • 常量池中的常量由二維表來表示 
    常量池開頭有個常量池容量計數器,接下來就全是一個個常量了,只不過常量都是由一張張二維表構成,除了記錄常量的值以外,還記錄當前常量的相關信息。
  • 常量池是class文件的資源倉庫
  • 常量池是與本class中其它部分關聯最多的部分
  • 常量池是class文件中空間佔用最大的部分之一 

(3)常量池中常量的類型

        剛纔介紹了,常量池主要存放兩大類常量:字面值常量 和符號引用。在此基礎上,根據常量的數據類型不同,又可以被細分爲14種常量類型。這14種常量類型都有各自的二維表示結構。每種常量類型的頭1個字節都是tag,用於表示當前常量屬於14種類型中的哪一個。

CONSTANT_Class_info常量爲例,它的二維表示結構如下: 

CONSTANT_Class_info表:

類型

名稱

數量

u1

tag

1

u2

name_index

1

tag表示常量的類型(當前常量爲CONSTANT_Class_info,因此tag的值應爲7,表示一個類或接口的全限定名); 

name_index表示這個類或接口全限定名的位置。它的值表示指向常量池的第幾個常量。它會指向一個CONSTANT_Utf8_info類型的常量,它的二維表結構如下: 

CONSTANT_Utf8_info表:

類型

名稱

數量

u1

tag

1

u2

length

1

u1

bytes

length

CONSTANT_Utf8_info表示字符串常量; 

tag表示當前常量的類型,這裏應該是1; 

length表示這個字符串的長度; 

bytes爲這個字符串的內容(採用縮略的UTF8編碼)

注:爲什麼Java中定義的類、變量名字必須小於64K? 

類、接口、變量等名字都屬於符號引用,它們都存儲在常量池中。而不管哪種符號引用,它們的名字都由CONSTANT_Utf8_info類型的常量表示,這種類型的常量使用u2存儲字符串的長度。由於2字節最多能表示65535個數,因此這些名字的最大長度最多隻能是64K。

注:什麼是UTF-8編碼?什麼是縮略UTF-8編碼? 

前者每個字符使用3個字節表示,而後者把1~127ASCII碼用1字節表示,某些字符用2字節表示,某些字符用3字節表示。 

 

  • Class文件的構成4:訪問標誌

      在常量池之後緊接着是2字節的訪問標誌。用來表示類或結構的訪問信息。如這個class文件是類還是接口?是否是annotation?是否被public修飾?是否被abstract修飾?是否被final修飾等。 

由於這些標誌都由是/否表示,因此可以用0/1表示。 

訪問標誌爲2字節,可以表示16位標誌,但JVM目前只定義了8種,未定義的寫0

  • Class文件的構成5:類索引、父類索引、接口索引集合

      類索引、父類索引、接口索引集合是用來表示當前class文件所表示類的名字、父類名字、接口們的名字。 

它們按照順序依次排列,類索引和父類索引各自使用一個u2類型的無符號常量,這個常量指向CONSTANT_Class_info類型的常量,該常量又指向CONSTANT_Utf8_info常量,其中的bytes字段記錄了本類、父類的全限定名。 

由於一個類的接口可能有好多個,因此需要用一個集合來表示接口索引,它在類索引和父類索引之後。這個集合頭兩個字節表示接口索引集合的長度,接下來就是接口的名字索引。 

  • Class文件的構成6:字段表的集合
(字段表集合1)  什麼是字段表集合?

    接下來是字段表的集合。字段表集合用於存儲本類所涉及到的成員變量(區別於常量池中的常量),包括實例變量和類變量,但不包括方法中的局部變量。 

每一個字段表只表示一個成員變量,本類中所有的成員變量構成了字段表集合。

(字段表集合2) 字段表結構的定義

類型

名稱

數量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

  • access_flags 
    字段的訪問標誌。在Java中,每個成員變量都有一系列的修飾符,和上述class文件的訪問標誌的作用一樣,只不過成員變量的訪問標誌與類的訪問標誌稍有區別。

    public volatile static transientenumfinalsynthetic(字段是否由編譯器自動生成)。每個標識都有一個標誌值如public(0x0001)final(0x0010)。最後進行或運算即可。

  • name_index 
        
    本字段名字的索引。指向一個CONSTANT_Class_info類型的常量,這裏面存儲了本字段的名字等信息。

值爲0x0005,指向常量池中一個CONSTANT_Utf8_info類型的常量,值爲字符串“j”。

  • descriptor_index 
          
    描述符。用於描述本字段在Java中的數據類型等信息(下面詳細介紹)

    如0x0006 表示常量池的字符串“I”。再結合上面的例子,推斷出源代碼應爲private int j;

  • attributes_count 
    屬性表集合的長度。
  • attributes 
    屬性表集合。到descriptor_index爲止是字段表的固定信息,光有上述信息可能無法完整地描述一個字段,因此用屬性表集合來存放額外的信息,比如一個字段的值爲100。(下面會詳細介紹)
(字段表集合3) 什麼是描述符?

  • 成員變量(包括類成員變量和實例成員變量)和 方法都有各自的描述符。 
  • 對於字段而言,描述符用於描述字段的數據類型; 
  • 對於方法而言,描述符用於描述字段的數據類型、參數列表、返回值。

在描述符中,基本數據類型用大寫字母表示,對象類型用“L對象類型的全限定名”表示,數組用“[數組類型的全限定名”表示。 "java.lang.String[][]" 被記錄爲 "[[Ljava/lang/String" ;“int[]”被記錄爲“[I”。

描述方法時,將參數根據上述規則放在()中,()右側按照上述方法放置返回值。而且,參數之間無需任何符號。如方法 int func(char[]ch1, int a, int b, char[] ch2, int c, int d)的描述符爲"([CII[CII)I"; 

(字段表集合4) 字段表集合的注意點
  • 一個class文件的字段表集合中不能出現從父類/接口繼承而來字段;
  • 一個class文件的字段表集合中可能會出現程序員沒有定義的字段                                                                                      如編譯器會自動地在內部類的class文件的字段表集合中添加外部類對象的成員變量,供內部類訪問外部類。
  • Java中只要兩個字段名字相同就無法通過編譯。但在JVM規範中,允許兩個字段的名字相同但描述符不同的情況,並且認爲它們是兩個不同的字段。  

  • Class文件的構成7:方法表的集合

在class文件中,所有的方法以二維表的形式存儲,每張表來表示一個函數,一個類中的所有方法構成方法表的集合。 

方法表的結構和字段表的結構一致,只不過訪問標誌和屬性表集合的可選項有少數不同。

類型

名稱

數量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

方法表的屬性表集合中有一張Code屬性表,用於存儲當前方法經編譯器編譯過後的字節碼指令。

方法表集合的注意點

  1. 如果本class沒有重寫父類的方法,那麼本class文件的方法表集合中是不會出現父類/父接口的方法表;
  2. 本class的方法表集合可能出現程序員沒有定義的方法 
    如編譯器在編譯時會在class文件的方法表集合中加入類構造器和實例構造器。
  3. 重載一個方法需要有相同的簡單名稱和不同的特徵簽名。JVM的特徵簽名和Java的特徵簽名有所不同: 
  • Java特徵簽名:方法參數在常量池中的字段符號引用的集合
  • JVM特徵簽名:方法參數+返回值

一個經典問題:爲什麼重寫返回值不構成重載?

【答】Java語言規範重載一個方法要求:1.方法名一樣;2特徵簽名與原方法不同。

方法的特徵簽名就是一個方法中各個參數在常量池中的字段符號引用的集合。

方法的特徵簽名包括方法名稱,參數的個數,類型,順序。卻不包含返回值。所以僅僅返回值不同是不能構成重載的。

  • Class文件的構成8:屬性表的集合 

限制寬鬆,不要求各個屬性表具有嚴格順序。在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用於描述某些場景專有的信息。

8.1 結構簡介

對於每一個屬性,有大致的表結構爲:

類型

名稱

數量

u2

attibute_name_index

1

u4

attribute_length

1

u1

info

attribute_length

attribute_name_index u2 : 名字,指向常量池UTF-8類型常量(一個字符串)

attribute_length u4 : 長度

info[attribute_length] u1 :內容  其實就是指定了長爲attribute_length字節的內存

attribute本身也可以包含其他attribute

8.2 部分屬性介紹

8.2.1 Deprecated——用於描述字段和方法或者類被廢棄

Deprecated屬性結構:

類型

名稱

描述

u2

attibute_name_index

指向常量池一個包含“Deprecated”字符串的UTF-8常量

u4

attribute_length

值恆爲0

8.2.2 ConstantValue

類中的一個常量定義爲“publicstatic int sid=99;”

類型

名稱

描述

u2

attibute_name_index

指向常量池一個包含“ConstantValue”字符串的UTF-8常量

u4

attribute_length

值恆爲2

u2

constantvalue_index u2

指向常量池中的常量值 UTF-8、Float、Double等

8.2.3 Code屬性

用一個結構體描述


舉例:


如果0到5字節碼出現java/lang/IllegalStateException異常,就跳轉到第8個字節碼去處理

catch_type指向常量池的第48號字符串是java/lang/IllegalStateException

 

LineNumberTable屬性


localVariableTable屬性

 

8.2.4 Exceptions屬性

表示方法拋出的異常

結構:

類型

名稱

描述

u2

attibute_name_index

指向常量池“Exceptions”字符串

u4

attribute_length

後面還有多少字節長度

u2

number_of_exceptions

拋了多少個異常

u2

exception_index_table[number_of_exceptions]

異常索引表

8.2.5 SourceFile屬性

結構:

類型

名稱

描述

u2

attibute_name_index

指向常量池“SourceFile”字符串

u4

attribute_length

固定爲2

u2

soucefile_index

指向常量池某個UTF-8型常量的類名字符串

 

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