【JVM虛擬機】(8)--深刻理解Class中--方法、屬性表集合

#<center>深刻理解Class中--方法、屬性表集合</center>html

以前有關class文件已經寫了兩篇博客:java

一、【JVM虛擬機】(5)---深刻理解JVM-Class中常量池jvm

二、【JVM虛擬機】(6)---深刻理解Class中訪問標誌、類索引、父類索引、接口索引函數

三、【JVM虛擬機】(7)---深刻理解Class中-屬性集合學習

那麼這篇博客主要講有關 方法表集合 相關的理解和代碼示例。ui

方法表集合: 告知該方法是什麼修飾符修飾?是否有方法值?返回類型是什麼?方法名稱,方法參數,還有就是方法內的一些信息。spa

<font color=#FFD700>1、方法集合概念 </font>

一、概念

方法表集合:方法表集合和屬性表集合其實很類似,都是由一個計數器(方法)若干個方法表構成,只不過方法表的結構相對複雜不少。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屬性

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底層而言,它能表示的機器操做碼很少於28 次方,即 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」類型的屬性表記錄着局部變量描述

二、ConstantValue屬性

之因此學習這個,是由於後面類加載機制有聯繫到這個屬性

這個屬性的做用是通知虛擬機爲靜態變量賦值,只要被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_PUBLICACC_STATICACC_SYNCHRONIZEDACC_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

相關文章
相關標籤/搜索