深入瞭解Java虛擬機(3)類文件結構

来源:http://www.cnblogs.com/zhangxinly/archive/2017/08/11/7296178.html
-Advertisement-
Play Games

虛擬機執行子系統 一、類文件結構 1.魔數和class版本 1.magic-魔數:0xCAFEBABE;4位元組 2.minor_version:次版本,丶之後的數字;2位元組 3.major_version:主版本,丶之前的數字;2位元組 2.常量池 1.constant_pool_count:常量池常 ...


 

 虛擬機執行子系統

一、類文件結構

 

1.魔數和class版本

  1.magic-魔數:0xCAFEBABE;4位元組

  2.minor_version:次版本,丶之後的數字;2位元組

  3.major_version:主版本,丶之前的數字;2位元組

2.常量池

  1.constant_pool_count:常量池常量數量(= 此值 - 1):2位元組

    由於常量池中常量的數量是不固定的,所以在常量池的入口需要放置一項u2類型的數據,代表常量池容量計數值。

  2.constant_pool:常量,第一位為類型位,之後的就是按照各自常量的定義:n位元組

    

   

   

   

3.訪問標識符

   1.access_flags:訪問標識

     這個標誌用於識別一些類或者介面層次的訪問信息,包括:這個Class是類還是介面;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。

   

    如:0x0001 0x0020說明是一個公共的類

4.類索引、父類索引、介面索引:Class文件中由這三項數據來確定這個類的繼承關係

  1.this_class:類索引:2位元組

    類索引用於確定這個類的全限定名

  2.super_class:父類索引:2位元組

    父類索引用於確定這個類的父類的全限定名。由於Java語言不允許多重繼承,所以父類索引只有一個,除了java.lang.Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不為0

  3.interfaces:介面索引:2位元組數組   

    介面索引集合就用來描述這個類實現了哪些介面,這些被實現的介面將按implements語句(如果這個類本身是一個介面,則應當是extends語句)後的介面順序從左到右排列在介面索引集合中

    介面索引開頭為數量:2位元組

  查找

    類索引、父類索引和介面索引集合都按順序排列在訪問標誌之後,類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字元串

    

5.欄位表集合 

可以包括的信息有:

  欄位的作用域(public、private、protected修飾 符)、是實例變數還是類變數(static修飾符)、可變性(final)、併發可見性(volatile修飾 符,是否強制從主記憶體讀寫)、可否被序列化(transient修飾符)、欄位數據類型(基本類型、對象、數組)、欄位名稱。上述這些信息中,各個修飾符都是布爾值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。

  欄位名、欄位數據類型,這些都是無法固定的,只能引用常量池中的常量來描述

   

  1.access_flags:欄位訪問標識:u2

    

  2.name_index:欄位簡單名稱:u2

    引用常量池常量

  3.descriptor_index:方法描敘符:u2

    描敘欄位:欄位類型

    描敘方法:(參數列表)描敘符

    引用常量池常量

     

 

 6.方法表集合

  

  

 

7.class、欄位、方法等的屬性表

  預定義的有21種,每種都有自己的結構

  1.Code屬性

    

 

    attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定為“Code”,它代表了該屬性的屬性名稱

    attribute_length指示了屬性值的長,所以屬性值的長度固定為整個屬性表長度減去6個位元組。

    max_stack代表了操作數棧(Operand Stacks)深度的最大值。在方法執行的任意時刻,操作數棧都不會超過這個深度。虛擬機運行的時候需要根據這個值來分配棧幀(StackFrame)中的操作棧深度。

    max_locals代表了局部變數表所需的存儲空間。

    code_length和code用來存儲Java源程式編譯後生成的位元組碼指令。

    code:位元組碼

    exception:異常表

      

 

      如果當位元組碼在第start_pc行[1]到第end_pc行之間(不含第end_pc行)出現了類型為catch_type或者其子類的異常(catch_type為指向一個CONSTANT_Class_info型常量的索引)

    則轉到第handler_pc行繼續處理。當catch_type的值為0時,代表任意異常情況都需要轉向到handler_pc處進行處理

//Java源碼
public int inc(){
    int x;
    try{
        x=1return x;
    }catch(Exception e){
        x=2return x;
    }finally{
        x=3;
    }
}/

/編譯後的ByteCode位元組碼及異常表
public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try塊中的x=1
1:istore_1
2:iload_1//保存x到returnValue中,此時x=1
3:istore 4
5:iconst_3//finaly塊中的x=3
6:istore_1
7:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
9:ireturn
10:astore_2//給catch中定義的Exception e賦值,存儲在Slot 2中
11:iconst_2//catch塊中的x=2
12:istore_1
13:iload_1//保存x到returnValue中,此時x=2
14:istore 4
16:iconst_3//finaly塊中的x=3
17:istore_1
18:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
20:ireturn
21:astore_3//如果出現了不屬於java.lang.Exception及其子類的異常才會走到這裡
22:iconst_3//finaly塊中的x=3
23:istore_1
24:aload_3//將異常放置到棧頂,並拋出
25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any            

 

      異常表實際上是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制

  2.Exception屬性

    方法的異常

    

    number_of_exceptions:表示方法可能拋出number_of_exceptions種受查異常

    exception_index_table:一個指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型

  3.LineNumberTable屬性

    LineNumberTable:描述Java源碼行號與位元組碼行號(位元組碼的偏移量)之間的對應關係它並不是運行時必需的屬性,但預設會生成到Class文件之中

    可以在Javac中分別使用-g:none或-g:lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性

    對程式運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,並且在調試程式的時候,也無法按照源碼行來設置斷點

    

      line_number_table:數量為line_number_table_length、類型為line_number_info的集合,

      line_number_info:表包括了start_pc和line_number兩個u2類型的數據項,前者是位元組碼行號,後者是Java源碼行號

  4.LocalVariableTable屬性

    LocalVariableTable:描述棧幀中局部變數表中的變數與Java源碼中定義的變數之間的關係,它也不是運行時必需的屬性

    但預設會生成到Class文件之中,可以在Javac中分別使用-g:none或-g:vars選項來取消或要求生成這項信息。

    如果沒有生成這項屬性,最大的影響就是當其他人引用這個方法時,所有的參數名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數名,這對程式運行沒有影響,但是會對代碼編寫帶來較大不便,而且在調試期間無法根據參數名稱從上下文中獲得參數值

    

    local_variable_info:代表了一個棧幀與源碼中的局部變數的關聯

    

    

    start_pc和length:分別代表了這個局部變數的生命周期開始的位元組碼偏移量及其作用範圍覆蓋的長度,兩者結合起來就是這個局部變數在位元組碼之中的作用域範圍。

    name_index和descriptor_index:指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變數的名稱以及這個局部變數的描述符。

    index:這個局部變數在棧幀局部變數表中Slot的位置。當這個變數數據類型是64位類型時(double和long),它占用的Slot為index和index+1兩個

    泛型的支持屬性

      在JDK 1.5引入泛型之後,LocalVariableTable屬性增加了一個“姐妹屬性”:LocalVariableTypeTable

      這個新增的屬性結構與LocalVariableTable非常相似,僅僅是把記錄的欄位描述符的descriptor_index替換成了欄位的特征簽名(Signature),

      對於非泛型類型來說,描述符和特征簽名能描述的信息是基本一致的,但是泛型引入之後,由於描述符中泛型的參數化類型被擦除掉

    描述符就不能準確地描述泛型類型了,因此出現了LocalVariableTypeTable。

  5.SourceFile屬性

    SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以分別使用Javac的-g:none或-g:source選項來關閉或要求生成這項信息。

    在Java中,對於大多數的類來說,類名和文件名是一致的,但是有一些特殊情況(如內部類)例外。如果不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名

    

    sourcefile_index:指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名

  6.ConstantValue屬性

    ConstantValue屬性的作用是通知虛擬機自動為靜態變數賦值。

    只有被static關鍵字修飾的變數(類變數)才可以使用這項屬性。

    對於非static類型的變數(也就是實例變數)的賦值是在實例構造器<init>方法中進行的;

    而對於類變數,則有兩種方式可以選擇:

      如果同時使用final和static來修飾一個變數,並且這個變數的數據類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進行初始化

      如果這個變數沒有被final修飾,或者並非基本類型及字元串,則將會選擇在<clinit>方法中進行初始化

    

    ConstantValue屬性是一個定長屬性

    attribute_length:數據項值必須固定為2。

    constantvalue_index:數據項代表了常量池中一個字面量常量的引用

      根據欄位類型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一種

   7.InnerClasses屬性

    InnerClasses屬性用於記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那編譯器將會為它以及它所包含的內部類生成InnerClasses屬性

    

    number_of_classes:代表需要記錄多少個內部類信息,每一個內部類的信息都由一個inner_classes_info表進行描述

    

    inner_class_info_index和outer_class_info_index:指向常量池中CONSTANT_Class_info型常量的索引,分別代表了內部類和宿主類的符號引用。

    inner_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個內部類的名稱,如果是匿名內部類,那麼這項值為0。

    inner_class_access_flags:內部類的訪問標誌,類似於類的access_flags

    

 

   8.Deprecated及Synthetic屬性

    Deprecated屬性用於表示某個類、欄位或者方法,已經被程式作者定為不再推薦使用,它可以通過在代碼中使用@deprecated註釋進行設置。

    Synthetic屬性代表此欄位或者方法並不是由Java源碼直接產生的,而是由編譯器自行添加的,

   9.StackMapTable屬性

    位元組碼驗證

  10.Signature屬性

     泛型被擦出後,獲取泛型信息

  11.BootstrapMethods屬性

    BootstrapMethods屬性在JDK 1.7發佈後增加到了Class文件規範之中,它是一個複雜的變長屬性,位於類文件的屬性表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。

 

 8.位元組碼指令簡介

  1.載入和存儲指令

    載入和存儲指令用於將數據在棧幀中的局部變數表和操作數棧(見第2章關於記憶體區域的介紹)之間來回傳輸,這類指令包括如下內容。  

    將一個局部變數載入到操作棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。

    將一個數值從操作數棧存儲到局部變數表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。

    將一個常量載入到操作數棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。

    擴充局部變數表的訪問索引的指令:wide。

  2.運算指令

    加法指令:iadd、ladd、fadd、dadd。
    減法指令:isub、lsub、fsub、dsub。
    乘法指令:imul、lmul、fmul、dmul。
    除法指令:idiv、ldiv、fdiv、ddiv。
    求餘指令:irem、lrem、frem、drem。
    取反指令:ineg、lneg、fneg、dneg。
    位移指令:ishl、ishr、iushr、lshl、lshr、lushr。
    按位或指令:ior、lor。
    按位與指令:iand、land。
    按位異或指令:ixor、lxor。
    局部變數自增指令:iinc。
    比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。

   3.類型轉換指令

    Java虛擬機直接支持(即轉換時無需顯式的轉換指令)以下數值類型的寬化類型轉換(Widening Numeric Conversions,即小範圍類型向大範圍類型的安全轉換):

      int類型到long、float或者double類型。

      long類型到float、double類型。

      float類型到double類型。
    處理窄化類型轉換(Narrowing Numeric Conversions)

      這些轉換指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。

   4.對象訪問與創建

    創建類實例的指令:new。

    創建數組的指令:newarray、anewarray、multianewarray。

    訪問類欄位(static欄位,或者稱為類變數)和實例欄位(非static欄位,或者稱為實例變數)的指令:getfield、putfield、getstatic、putstatic。

    把一個數組元素載入到操作數棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。

    將一個操作數棧的值存儲到數組元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。

    取數組長度的指令:arraylength。

    檢查類實例類型的指令:instanceof、checkcast。

  5.操作數棧管理指令

    如同操作一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用於直接操作操作數棧的指令,

    包括:

      將操作數棧的棧頂一個或兩個元素出棧:pop、pop2。

      複製棧頂一個或兩個數值並將複製值或雙份的複製值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。

      將棧最頂端的兩個數值互換:swap

  6.控制轉移指令

    條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。

    複合條件分支:tableswitch、lookupswitch。

    無條件分支:goto、goto_w、jsr、jsr_w、ret。

  7.方法調用和返回

    invokevirtual指令用於調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派),這也是Java語言中最常見的方法分派方式。

    invokeinterface指令用於調用介面方法,它會在運行時搜索一個實現了這個介面方法的對象,找出適合的方法進行調用。

    invokespecial指令用於調用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。

    invokestatic指令用於調用類方法(static方法)。

    invokedynamic指令用於在運行時動態解析出調用點限定符所引用的方法,並執行該方法,前面4條調用指令的分派邏輯都固化在Java虛擬機內部,

    invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明為void的方法、實例初始化方法以及類和介面的類初始化方法使用

  8.異常處理指令

    在Java程式中顯式拋出異常的操作(throw語句)都由athrow指令來實現

    除了用throw語句顯式拋出異常情況之外,Java虛擬機規範還規定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出。

    例如,在前面介紹的整數運算中,當除數為零時,虛擬機會在idiv或ldiv指令中拋出ArithmeticException異常。

    而在Java虛擬機中,處理異常(catch語句)不是由位元組碼指令來實現的(很久之前曾經使用jsr和ret指令來實現,現在已經不用了),而是採用異常表來完成的。

  9.同步指令

    同步一段指令集序列通常是由Java語言中的synchronized語句塊來表示的

    Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義

void onlyMe(Foo f){
    synchronized(f){
    doSomething();
    }
}

// 指令碼
Method void onlyMe(Foo)
0 aload_1//將對象f入棧
1 dup//複製棧頂元素(即f的引用)
2 astore_2//將棧頂元素存儲到局部變數表Slot 2中
3 monitorenter//以棧頂元素(即f)作為鎖,開始同步
4 aload_0//將局部變數Slot 0(即this指針)的元素入棧
5 invokevirtual#5//調用doSomething()方法
8 aload_2//將局部變數Slow 2的元素(即f)入棧
9 monitorexit//退出同步
10 goto 18//方法正常結束,跳轉到18返回
13 astore_3//從這步開始是異常路徑,見下麵異常表的Taget 13
14 aload_2//將局部變數Slow 2的元素(即f)入棧
15 monitorexit//退出同步
16 aload_3//將局部變數Slow 3的元素(即異常對象)入棧
17 athrow//把異常對象重新拋出給onlyMe()方法的調用者
18 return//方法正常返回
Exception table:
FromTo Target Type
4 10 13 any
13 16 13 any        

 

 

 

 

 

 

    

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 15.1 枚舉類型 枚舉定義的符號是常量值. C 編譯器編譯時,會用數值替換符號,不再引用定義了符號的枚舉類型.可能會出現一些版本問題. Enum.IsDefined(Type enumType, object value) 方法被經常用於參數校驗: IsDefined 方法必須慎用. 首先, Is ...
  • 前言:AutoMapper的下載安裝我就不多說了,網上百度一大堆。今天我就說說它的更為簡單的使用,什麼叫更為簡單呢?按照一般的使用方法,我們首先建DTO,然後建每個對應的Profile,然後還要把每個Profile給Initialize,最後Global里AutoMapper.Configurati ...
  • Windows Azure是微軟的雲平臺,可以提供廣泛服務。您可以通過它搭建、部署並管理解決方案,用於實現您可以想象的幾乎任何目標。換言之,WindowsAzure是擁有無限可能的世界。無論您是需要運行伺服器工作負載的橫跨廣闊地域的型企業,還是希望將網站面向各地都可以提供高速訪問的小型公司,Wind... ...
  • 為了讓廣大 IT 同胞度過一個美好的7天長假,小編今天特意為大家準備了一份IT人長假不加班正確姿勢指南,幫助大家實現長假我做主的美好願望。 ...
  • 題目背景 眾所周知,我們稱g是a的約數,當且僅當g是正數且a mod g = 0。 眾所周知,若g既是a的約數也是b的約數,我們稱g是a、b的一個公約數。 眾所周知,a、b最大的那個公約數就叫最大公約數。 題目描述 現在對於給定的兩個正整數a、b,你需要求出它們次大的公約數(second great ...
  • Python列表的增刪改查排 一、列表的樣子: a = [‘q’ , ’w’ , ’e ’, ’r’,‘t’] a為列表名,[ ]為列表內容,‘ ’為列表內的元素,‘q’為a[0] 二、查(也稱切片): print ( a[0] ) #取出列表中第一個元素 即:q print ( a[1:] ) # ...
  • JAVA記憶體模型: 堆區中 保存呢以new關鍵字創建出的對象 jdk1.7版本之後 字元串常量池也存放在堆區中 棧區中 保存臨時變數和參數,每個線程都有自己的棧,每調用一個方法創建一個棧針 方法區 靜態成員 類 方法等信息 調用方法的時候 如果參數是基本數據類型,那麼傳遞的是數值 如果參數是引用類型 ...
  • 文件下載,可以是post請求,也可以是get請求。 新建web項目,在WebRoot下建up目錄存放上傳的文件: 最簡單的但是實際不會這樣做的下載方式,直接用a標簽指向文件目錄,就能下載: 這樣能下載,但是任何人都能下載,你沒法做一些控制,比如只有登錄的用戶才能下載,積分不夠的人不能下載,而且,這樣 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...