#<center>深刻理解Class中--方法、屬性表集合</center>html
以前有關class文件已經寫了兩篇博客:java
一、【JVM虛擬機】(5)---深刻理解JVM-Class中常量池jvm
二、【JVM虛擬機】(6)---深刻理解Class中訪問標誌、類索引、父類索引、接口索引函數
三、【JVM虛擬機】(7)---深刻理解Class中-屬性集合學習
那麼這篇博客主要講有關 方法表集合 相關的理解和代碼示例。ui
方法表集合
: 告知該方法是什麼修飾符修飾?是否有方法值?返回類型是什麼?方法名稱,方法參數,還有就是方法內的一些信息。spa
方法表集合
:方法表集合和屬性表集合其實很類似,都是由一個計數器(方法)
和若干個方法表
構成,只不過方法表的結構相對複雜不少。3d
方法表的結構體
:訪問標誌(access_flags)、名稱索引(name_index)、描述索引(descriptor_index)、屬性表(attribute_info)集合組成。code
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
1)、訪問標誌
htm
很少說了,和屬性中的其實差很少,只是有些修飾符不同。
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235540325-1150347478.jpg" style="border: 1px dashed rgb(204, 200, 200);" width="600" height="326">
2)、名稱索引
就是指這個方法的名稱。如:'public void getXX()'中,getXX就是名稱索引。名稱索引佔兩個字節,這個方法的名稱以UTF-8格式的字符串存儲在這個常量池項中。
3)、描述索引
指這個方法的返回值,方法內參數信息。一個方法的描述包含若干個參數的數據類型和返回值的數據類型。
4)、屬性表(attribute_info)集合
下面講
##<font color=#FFD700> 2、屬性表集合 </font>
在Class文件、字段表、方法表均可以攜帶本身的屬性表集合,用於描述某些場景專有的信息。 在方法表中, 屬性表集合記錄了某個方法的一些屬性信息,這些信息包括:
方法的可執行的機器指令
拋出的異常信息
被@deprecated註解表示
編譯器自動生成的
**屬性表(attribute_info)**結構體的通常結構以下所示:
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235716144-1459133840.jpg" style="border: 1px dashed rgb(204, 200, 200);" width="600" height="550">
屬性表佔着很是大的一部分且定義了衆多屬性,上面只列舉了4個,查看完成的:JDK1.7版本中21項屬性表集合簡要介紹
下面介紹兩個重要的屬性
code屬性比較複雜,它是通過編譯器編譯成字節碼指令以後的數據。就是說java程序中的方法體通過javac編譯器處理後,最終變成字節碼存儲在Code屬性內
。
並不是全部方法表都有這個屬性,接口和抽象類就沒有【沒有方法體】。 Code屬性是Class文件中最重要的一個屬性,在Class文件中,Code屬性用於描述代碼,全部的其它數據項目都用來描述元數據,瞭解code屬性對了解字 節碼執行引擎來講是必要基礎。
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235758419-1368030226.png" style="border: 1px dashed rgb(204, 200, 200);" width="600" height="365">
Code屬性表的組成部分:
機器指令——code
目前的JVM使用一個字節表示機器操做碼,即對JVM底層而言,它能表示的機器操做碼很少於2的 8 次方,即 256個。class文件中的機器指令部分是class文件中最重要的部分,而且很是複雜。
異常處理跳轉信息
若是代碼中出現了**try{}catch{}
塊,那麼try{}塊內的機器指令的地址範圍記錄下來,而且記錄對應的catch{}塊中的起始機器指令地址,當運行時在try塊中有異常拋出的話,JVM會將catch{}塊對應懂得其實機器指令地址傳遞給PC寄存器**,從而實現指令跳轉;
Java源碼行號和機器指令的對應關係
---LineNumberTable屬性表
編譯器在將java源碼編譯成class文件時,會將源碼中的語句行號跟編譯好的機器指令關聯起來,這樣的class文件加載到內存中並運行時,若是拋出異常,JVM能夠根據這個對應關係,拋出異常信息,告訴咱們咱們的源碼的多少行有問題,方便咱們定位問題。
Code屬性表結構體:
一、attribute_name_index
: 屬性名稱索引,佔有2個字節,其內的值指向了常量池中的某一項,該項表示字符. 串「Code」;
二、attribute_length
: 屬性長度,佔有 4個字節,其內的值表示後面有多少個字節是屬於此Code屬性表的;
三、max_stack
: 操做數棧深度的最大值,佔有 2 個字節,在方法執行的任意時刻,操做數棧都不該該超過這個值,虛擬機的運行的時候,會根據這個值來設置該方法對應的棧幀(Stack Frame)中的操做數棧的深度;
四、max_locals
最大局部變量數目,佔有 2個字節,其內的值表示局部變量表所須要的存儲空間大小;
五、code_length
: 機器指令長度,佔有 4 個字節,表示跟在其後的多少個字節表示的是機器指令;
六、code
機器指令區域,該區域佔有的字節數目由 code_length中的值決定。JVM最底層的要執行的機器指令就存儲在這裏;
七、exception_table_length
: 顯式異常表長度,佔有2個字節,若是在方法代碼中出現了try{} catch()形式的結構,該值不會爲空,緊跟其後會跟着若干個exception_table結構體,以表示異常捕獲狀況;
八、exception_table
: 顯式異常表,佔有8 個字節,start_pc,end_pc,handler_pc中的值都表示的是PC計數器中的指令地址。exception_table表示的意思是:若是字節碼從第start_pc行到第end_pc行之間出現了catch_type所描述的異常類型,那麼將跳轉到handler_pc行繼續處理。
九、attribute_count
: 屬性計數器,佔有 2 個字節,表示Code屬性表的其餘屬性的數目
十、attribute_info
: 表示Code屬性表具備的屬性表,它主要分爲兩個類型的屬性表:「LineNumberTable」類型和「LocalVariableTable」類型。「LineNumberTable」類型的屬性表記錄着Java源碼和機器指令之間的對應關係「LocalVariableTable」類型的屬性表記錄着局部變量描述
之因此學習這個,是由於後面類加載機制有聯繫到這個屬性
這個屬性的做用是通知虛擬機爲靜態變量賦值,只要被static修飾的變量纔有這個屬性,【有該屬性的字段必須有ACC_STATIC訪問標誌,反過來不必定】。
對於 "int x = 123" 和 "static int x =123"這類代碼在平常編寫中很常見,但虛擬機對這兩種變量賦值的時刻卻不一樣。 對於非static變量[實例變量],是在實例構造器<init>進行 對於類變量,有兩種方式選擇 ①在類構造器<clinit>方法中賦值 ②使用ConstantValue屬性初始化 目前Sun javac編譯器是這麼作的【具體咋作不知道 = =】,若是同時使用final和static修飾一個變量[這種修飾就至關於個常量],而且是String或基本類型,就使用②, 若是沒有被final修飾或不是基本類型和String,就選擇①在<clinit>方法中初始化 //有關這點我在上篇博客舉過例子,最後幾句話也對這個解釋的很清楚。
<br>
##<font color=#FFD700> 3、示例 </font>
有關方法的代碼示例,我就不親自測了,由於有位博主寫的已經很清晰啦,我本身寫也沒那麼清晰。
public static synchronized final void greeting(){ }
**greeting()**方法的修飾符有:public、static、synchronized、final 這幾個修飾符修飾,那麼相對地,
greeting()方法的訪問標誌中的ACC_PUBLIC
、ACC_STATIC
、ACC_SYNCHRONIZED
、ACC_FINAL
標誌位都應該是1
從上面第一張圖能夠得出,該訪問標誌的值應該是十六進制0x0039
。
緊接着訪問標誌(access_flags)後面的兩個字節,叫作名稱索引(name_index),這兩個字節中的值是指向了常量池中某個常量池項的索引,該常量池項表示這這個方法名稱的字符串。
方法描述符索引(descrptor_index)是緊跟在名稱索引後面的兩個字節,這兩個字節中的值跟名稱索引中的值性質同樣,都是指向了常量池中的某個常量池項。這兩個字節中的指向的常量池項,是表示了方法描述符的字符串。
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235837914-1194106857.jpg" style="border: 1px dashed rgb(204, 200, 200);" width="600" height="234">
package com.louis.jvm; public class Simple { public static synchronized final void greeting(){ int a = 10; } }
1)、 Simple.class文件以下所示
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235905646-2145707810.png" style="border: 1px dashed rgb(204, 200, 200);" width="600" height="300">
注意
:方法表集合的頭兩個字節,即方法表計數器(method_count)的值是0x0002,它表示該類中有2 個方法。注意到,咱們的Simple.java中就定義了一個greeting()方法,爲何class文件中會顯示有兩個方法呢?
緣由
:若是咱們在類中沒有定義實例化構造方法,JVM編譯器在將源碼編譯成class文件時,會自動地爲這個類添加一個不帶參數的實例化構造方法,這種添加是字節碼級別的,JVM對全部的類實例化構造方法名採用了相同的名稱:「<init>」。若是咱們顯式地以下定義Simple()構造函數,這個類編譯出來的class文件和上面的不帶Simple構造方法的Simple類生成的class文件是徹底相同的。
2)、Simple.class 中的<init>() 方法
<img src="https://img2018.cnblogs.com/blog/1090617/201904/1090617-20190417235935464-1875058370.png" style="border: 1px dashed rgb(204, 200, 200);" width="700" height="800">
解釋:
一、方法訪問標誌(access_flags)
: 佔有 2個字節,值爲0x0001,即標誌位的第 16 位爲 1,因此該**<init>()**方法的修飾符是:ACC_PUBLIC;
二、 名稱索引(name_index)
: 佔有 2 個字節,值爲 0x0004,指向常量池的第 4項,該項表示字符串'<init>',即該方法的名稱是'<init>';
三、描述符索引(descriptor_index)
: 佔有 2 個字節,值爲0x0005,指向常量池的第 5 項,該項表示字符串「()V」,即表示該方法不帶參數,而且無返回值(構造函數確實也沒有返回值);
四、屬性計數器(attribute_count)
: 佔有 2 個字節,值爲0x0001,表示該方法表中含有一個屬性表,後面會緊跟着一個屬性表;
五、屬性表的名稱索引(attribute_name_index)
:佔有 2 個字節,值爲0x0006,指向常量池中的第6 項,該項表示字符串「Code」,表示這個屬性表是Code類型的屬性表;
六、 屬性長度(attribute_length)
:佔有4個字節,值爲0x0000 0011,即十進制的 17,代表後續的 17 個字節能夠表示這個Code屬性表的屬性信息;
七、 操做數棧的最大深度(max_stack)
:佔有2個字節,值爲0x0001,表示棧幀中操做數棧的最大深度是1;
八、局部變量表的最大容量(max_variable)
:佔有2個字節,值爲0x0001, JVM在調用該方法時,根據這個值設置棧幀中的局部變量表的大小;
九、 機器指令數目(code_length)
:佔有4個字節,值爲0x0000 0005,表示後續的5 個字節 0x2A 、0xB七、 0x00、0x0一、0xB1表示機器指令;
十、機器指令集(code[code_length])
:這裏共有 5個字節,值爲0x2A 、0xB七、 0x00、0x0一、0xB1;
十一、顯式異常表集合(exception_table_count)
: 佔有2 個字節,值爲0x0000,表示方法中沒有須要處理的異常信息;
十二、Code屬性表的屬性表集合(attribute_count)
: 佔有2 個字節,值爲0x0000,表示它沒有其餘的屬性表集合,由於咱們使用了**-g:none** 禁止編譯器生成Code****屬性表的 LineNumberTable 和LocalVariableTable;
解釋下機器指令集:
第一個字節 **0x2A
:查詢Java 虛擬機規範中關於操做碼的解釋,0x2A 對應的操做是"aload_0",做用是將第一個引用類型局部變量推送至棧頂;
第二個字節 0xB7
:0xB7 對應的操做是:"invokespecial",做用是調用超類構造方法、實例初始化方法或私有方法;它****帶有2個字節的參數,即後面的 0x00、0x01 是它的參數,這個參數是某個常量池中的索引,指向了常量池的第一項,該項表示一個方法引用項CONSTANT_Methodref_info結構體,表示java.lang.Object 類中的**<init>()方法,即 java/lang/Object."<init>":()V。這條指令的意思就是調用父類Object的構造方法<init>()**;
第5個字符是0xB1
: 對應操做是:「Ireturn」,做用是表示無返回值的方法返回,結束方法調用,這條語句放在方法的機器碼最後,表示方法結束調用,返回。
咱們可使用javap -v Simple > Simple.txt,查看反編譯信息是怎樣顯示這一信息的:
3)Simple.class 中的greeting() 方法
解釋:
一、方法訪問標誌(access_flags)
: 佔有 2個字節,值爲 0x0039 ,即二進制的00000000 00111001,即標誌位的第十一、十二、1三、16位爲1,根據上面講的方法標誌位的表示,能夠獲得該**greeting()**方法的修飾符有:ACC_SYNCHRONIZED、ACC_FINAL、ACC_STATIC、ACC_PUBLIC;
二、 名稱索引(name_index)
: 佔有 2 個字節,值爲 0x0007,指向常量池的第 7 項,該項表示字符串「greeting」,即該方法的名稱是「greeting」;
三、描述符索引(descriptor_index)
: 佔有 2 個字節,值爲0x0005,指向常量池的第 5 項,該項表示字符串「()V」,即表示該方法不帶參數,而且無返回值;
四、屬性計數器(attribute_count)
: 佔有 2 個字節,值爲0x0001,表示該方法表中含有一個屬性表,後面會緊跟着一個屬性表;
五、屬性表的名稱索引(attribute_name_index)
:佔有 2 個字節,值爲0x0006,指向常量池中的第6 項,該項表示字符串「Code」,表示這個屬性表是Code類型的屬性表;
六、屬性長度(attribute_length)
:佔有4個字節,值爲0x0000 0010,即十進制的16,代表後續的16個字節能夠表示這個Code屬性表的屬性信息;
七、操做數棧的最大深度(max_stack)
:佔有2個字節,值爲0x0001,表示棧幀中操做數棧的最大深度是1;
八、 局部變量表的最大容量(max_variable)
:佔有2個字節,值爲0x0001, JVM在調用該方法時,根據這個值設置棧幀中的局部變量表的大小;
九、器指令數目(code_length)
:佔有4 個字節,值爲0x0000 0004,表示後續的4個字節0x十、 0x0A、 0x3B、0xB1的是表示機器指令;
十、機器指令集(code[code_length])
:這裏共有4 個字節,值爲0x十、 0x0A、 0x3B、0xB1 ;
十一、顯式異常表集合(exception_table_count)
: 佔有2 個字節,值爲0x0000,表示方法中沒有須要處理的異常信息;
12 Code屬性表的屬性表集合(attribute_count)
: 佔有2 個字節,值爲0x0000,表示它沒有其餘的屬性表集合,由於咱們使用了**-g:none** 禁止編譯器生成Code****屬性表的 LineNumberTable 和LocalVariableTable;
指令集解釋
第一個字節 0x10
: 查詢Java虛擬機規範中關於操做碼的解釋,0x10 對應的操做是"bipush"," 做用是將單字節的常量值(-128~127) 推送至棧頂,它要求一個參數,後面的 0x0A 便是須要推送到棧頂的單字節,注意這裏的 0x0A 是16進制,就是咱們在代碼裏寫的"a=10"中的10。
第三個字節"3B"
: 「3B」對應的操做是:"istore_0",做用是將棧頂int 型數值存入第一個局部變量。咱們在greeting() 方法中就聲明瞭一個局部變量a,JVM的運行的時候,將這個局部變量a解析,並放置到局部變量表中的第一個位置;上述的0x10 0x0A 指令已經將0x0A 推送到了棧頂了,而後 0x3B指令便將棧頂的0x0A 取出,賦值給局部變量表中的第一個參數,即局部變量a,這樣就完成了對局部變量a的賦值;
第4個字符是0xB1
: 對應操做是:「Ireturn」,做用是表示無返回值的方法返回,結束方法調用,這條語句放在方法的機器碼最後,表示方法結束調用,返回。
咱們可使用javap -v Simple > Simple.txt,查看反編譯信息是怎樣顯示這一信息的:
<br>
###<font color=#FFD700> 參考 </font>
這篇文章基本上是參考,很是感謝做者分享,寫的很清楚:《Java虛擬機原理圖解》 <br> <br>
只要本身變優秀了,其餘的事情纔會跟着好起來(少將6)
原文出處:https://www.cnblogs.com/qdhxhz/p/10727117.html