JVM之類文件結構——下篇(字段、方法和屬性)

LZ水平有限,如果發現有錯誤之處,歡迎大家指出,或者覺得那塊說的不好,歡迎建議。希望和大家一塊討論學習
LZ QQ:1310368322


上篇我們着重討論了class文件中的常量池,接下來我們來看看下面的十六進制數到底是什麼含義?以及它們和我們的Java源碼之間有什麼聯繫?
訪問標誌
常量池之後,緊接着的兩個字節代表了訪問標誌(access_flags),這個標誌用於識別一些類或者接口層次的訪問信息,後面還會有關於方法的訪問標誌。
下面是類或接口訪問標誌的一些標誌值和含義
這裏寫圖片描述
我們先看看class文件中的這個標誌值爲多少
這裏寫圖片描述
0x00 21,表中沒有這個值啊,這是爲什麼呢?其實這是因爲有時候這個訪問標誌有可能同時滿足好幾種類型,所以就把他們的標誌值進行了或運算了,我這個HelloWorld 類是 public 的,所以我滿足 ACC_PUBLIC, 值爲 0x00 01,又因爲我的JDK的版本是 1.7 的,所以我滿足 ACC_SUPER, 值爲 0x00 20,兩者相或: 0x0001 | 0x0020 = 0x0021.
我們再來看看我們的HelloWorld的源碼中的類訪問標誌
這裏寫圖片描述

類索引、父類索引與接口索引集合
接下來就是類索引、父類索引和接口索引集合了,類索引和父類索引都是一個U2類型[佔兩個字節]的數據,而接口索引集合是一組U2類型的數據的集合(相當於一個存放U2類型的數組)。Class文件中由這三項數據來確定這個類的繼承關係。類索引用於確定這個類的權限定名[上篇已經介紹過了],父類索引用於確定這個類的父類的權限定名。而對於接口索引集合,在父類所以之後的兩個字節代表着接口計數器,代表着接口索引的容量(個數),這裏的HelloWorld不牽扯接口,所以這個值爲0
下來看看Class文件中的類索引、父類索引和接口索引集合
這裏寫圖片描述
我們看到類索引的值是0x00 01,上面已經說過,這個類索引是確定這個類的全限定名的,那是如果“確定”的呢?很容易相當,這些全限定名是存儲在常量池中的,肯定是通過這個索引去找的。如下圖
這裏寫圖片描述
在javap 反編譯中的常量池中是這樣的
這裏寫圖片描述
父類索引找權限定名的方法一樣,由於沒有接口,所以這裏不再討論
字段表集合
接下來就是關於字段的一些信息了,字段表用於描述接口或者類中聲明的變量, 我們想一想,Java中的字段的信息在Class中如何描述呢?字段的作用域(public、private、protected修飾符)、是實例變量還是類變量(是否被static修飾)等等,這些信息用標誌位來描述是很自然方便的,比如說,我用0x00 08代表被static修飾。而一些字段的名字、字段的數據類型(包括自定義數據類型)等等,這些很難固定(情況很多),所以我們只用將其存到常量池中,引用常量池來描述。
下來我們看看一個字段都包含哪些東西
這裏寫圖片描述
其中的第一項Access_flags 對應的字段訪問標誌表如下
這裏寫圖片描述
第二項的name_index 和 第三項的 descriptor_index 分表代表着字段的名稱索引 和 字段的描述符索引
字段的名稱索引很好理解,就是指向字段的名稱(在常量池中)的索引,而字段的描述符是什麼?這裏詳細的說說
比較嚴謹的描述是:描述符的作用是用來描述字段的數據類型。方法的參數列表(包括數量、類型以及順序)和返回值,這個可能不是很直觀,簡單來說,對於字段而言,就是字段的類型,比如 public int i = 0; 其中 int 類型就是該字段的描述符,在Class文件中 , int 類型用 字符“I”來表示,所以字段描述符的索引就是指向了該字段的描述符(在常量池中),字段中還有可能有屬性,比如說,你的代碼中有final static int i = 123; 這個字段就會有一個ConstantValue屬性,其值指向常量123.
我們之前寫的HelloWorld沒有字段,我們現在添加一個字段,如下:
這裏寫圖片描述
然後我們在來看看Class文件有什麼變化
這裏寫圖片描述
我們可以看到,後面多了字段的一些信息了,其中的 fields_count 值爲0x00 01,說明我們的字段只有一個,access_flags值爲0x00 01,查表可知我們這個字段是被public修飾的,name_index的值爲0x00 05,說明這個字段的名稱是在常量池的第五項常量中存着,用javap反編譯一下,如下圖:
這裏寫圖片描述
我們可以看到字段的名稱爲 i, 同時 字段的描述符爲 I(int 類型的)
因爲我們這裏的字段沒有屬性,所以attributes_count的值就爲0,後面就沒有attribute_info了
方法表集合
方法表和字段表非常相似,上面的字段表比較簡單,沒有屬性,接下來我們討論的方法表有屬性,稍微有點繞,但是仔細研究就清楚了。
同屬性表一樣,我們先來看看方法表中都有什麼東西(方法表結構),如下圖:
這裏寫圖片描述
可以看到,這個表結構和字段的表結構完全一樣,其含義就不再贅述了。有的讀者可能會問:方法和字段不太一樣啊,方法體裏面還有好多代碼,這些代碼對應在字節碼中的哪裏?其實這些代碼就是在方法表中的屬性中,有一個“Code”屬性專門存這些執行指令。
同樣方法的訪問標誌表如下:
這裏寫圖片描述
由於前面的HelloWorld沒有聲明方法,所以我們修改源代碼,加上一個簡單的 fun 方法
如下:
這裏寫圖片描述
還是老方法,我們看看源碼對應的字節碼,以及字節碼都代表什麼意思?
這裏寫圖片描述
和屬性表一樣,最前面的是methods_count,可以看出,Class文件中有三個方法,大家應該能猜到都要啥方法吧,構造方法、fun方法和main方法。在具體分析這個方法裏的一些細節問題,我們先來看幾個常用的屬性
① Code屬性: Java程序方法體中的代碼經過Javac編譯處理後,最終變爲字節碼指令存儲在Code屬性內。Code屬性在方法表的屬性集合之中
它的結構如下:
這裏寫圖片描述attribute_name_indexCode屬性的名字的索引,在常量池中,值爲Code,attribute_length代表Code屬性的長度,注意:這裏的長度不包含attribute_name_index 和 attribute_length本身的長度,也就是說整個Code屬性表的長度減去attribute_name_index和attribute_length本身所佔的字節數就是Code屬性值的長度。
下來簡單介紹一個下面各個字節的含義,這些含義在下次的執行引擎中會詳細的介紹。
max_stack: 操作數棧的最大值
max_locals:局部變量表所需的存儲空間
code_length: code值得長度
code:方法體中的代碼編譯後的字節碼
exception_info: 異常信息
attribute_info: Code 屬性的屬性(可以看成子屬性)
② LineNumberTable屬性:
屬性結構如下圖
這裏寫圖片描述
[稍後詳細介紹]
③ LocalVariableTable屬性:
屬性結構如下圖:
這裏寫圖片描述
對照着方法表的結構和這些屬性(主要是看佔了幾個字節),我們來分析一下第一個方法的字節碼文件,由於WinHex軟件中地方太小,我在其他軟件重新畫了一個圖,如下:
這裏寫圖片描述
其中的一些索引(如name_index、descriptor_index等),都是可以在常量池中查出來的。
其中Code屬性在javap 中如下圖:
這裏寫圖片描述
接下來我們來詳細看看LineNumberTable中的東西
這裏寫圖片描述
上文貼的LineNumberTable屬性結構中的line_number_info,這裏說明一下,line_number_info 表包括了 start_pc 和 line_number 兩個 u2 類型的數據項,前者是字節碼行號,後者是Java源碼行號,這裏的字節碼行號指的是code屬性中code值中的字節碼偏移量,也就是上面用Javap打印出Code屬性中code值最左邊的數字(後面有個冒號),這裏要特別注意: javap 打印出來的 LineNumberTable 中 line 3 : 0,前面的是Java源碼中的行號,後面對應的是字節碼行號,而在字節碼中的一對行號(字節碼行號和Java源碼行號)的順序是:前面是字節碼行號後面是Java源碼行號,如下:
這裏寫圖片描述
這裏寫圖片描述
然後我們再來詳細看看LocalVariableTable屬性
將方法表中的LocalVariableTable字節碼截取下來,根據LocalVariableTable的屬性結構和local_variable_info項目結構(上面已經給出),可以畫出下面的圖
這裏寫圖片描述
這裏需要注意一個概念,Slot,Slot是局部變量中最小的單元,對於byte、char這樣長度不超過32位的數據類型,在局部變量中佔一個Slot,而double和long這樣64位的數據類型則需要兩個Slot來存放
Slot在一個線程的函數棧幀中,如下圖
這裏寫圖片描述
還有一個需要理解的就是,start_pc和length屬性,start_pc指的是這個局部變量的聲明週期開始的字節碼偏移量,其實就是Code屬性中真正的code值中的字節碼偏移量,我們這個函數的start_pc的值爲0x00 00,就是說我這個局部變量(this)的聲明週期是從0: aload_0[這個就是前面用javap打印出來的Code屬性中的code值得第一行] 處開始,然後length指的是我這個局部變量的作用範圍的長度,其實就是我從字節碼的開始c處,我能夠作用於多長的字節碼,這個構造函數的this局部變量的length爲:0x00 0A,長度爲10,就是整個code值中的所有指令我都可以作用得到,從0: aload_0 到 9: return,這兩個合起來的意思就是這個局部變量在字節碼之中的作用域範圍。name_index和descriptor_index和前面所代表的的意思一樣,還有一個index是局部變量在棧幀局部變量表中Slot的位置。
最後把這幾個屬性串起來,他們的關係如下:
這裏寫圖片描述
最後再看一下一個小屬性,也是這個Class文件的最後的東西了
老規矩,先看一下SourceFile屬性的屬性結構
這裏寫圖片描述
sourcefile_index是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名。
這裏寫圖片描述
注意:方法表之後緊跟着是屬性的個數(attributes_count)
其中的Code屬性會在下一篇執行引擎詳細介紹

本文參考《深入理解Java虛擬機》—周志明

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