Class文件十六進制背後的祕密

不管你是跟同事、同窗、上下級、同行、或者面試官討論技術問題的時候,很容易捲入JVM大型撕逼現場。爲了可以讓你們從大型撕逼現場中脫穎而出,最近我苦思冥想如何把知識點儘量呈現的容易理解,方便記憶。因而就開啓了這一系列文章的編寫。爲了讓JVM相關知識點可以造成一個體系,arthinking將編寫整理一系列的專題,以儘可能以圖片的方式描述相關知識點,而且最終把全部相關知識點串成了一張圖。持續更新中,歡迎你們閱讀。有任何錯落之處也請您高擡貴手幫忙指正,感謝!html

導讀

Java源代碼被編譯爲Class文件以後,裏面究竟保存了什麼東西,有什麼奧祕呢?本文將爲你揭開Class文件神祕的面紗。Class文件結構是JVM加載Class,實例化對象,和進行方法調用的重要依據,瞭解了它,咱們將可以更透徹的洞悉JVM執行字節碼背後的機制:java

  1. 運行時常量池和靜態常量池有什麼區別?
  2. Class文件裏面都有什麼內容?
  3. Class文件反彙編以後的格式裏面分別有什麼,嘗試解讀裏面方法中的彙編指令
  4. 本地變量表和操做數棧是如何工做的

一、查看Class文件

若是想要探究Class文件十六進制數字背後的祕密,那就必須得翻出字節碼來仔細研究一下了,看一下里面到底是什麼東西。這裏提供一個查看字節碼文件的命令:面試

一、以十六進制查看Class文件算法

技巧:vim + xxd = 十六進制編輯器spring

vim -b xxx.class 能夠以二進制將class文件打開;數據庫

vim內調用::%!xxd 以十六進制顯示當前文件;編程

修改完成以後,若是想保存,則執行如下命令把十六進制轉換回二進制:bootstrap

:%!xxd -rvim

二、輸出包括行號,本地變量反彙編等信息windows

javap

  • -v -verbose:輸出附加信息(包括行號、本地變量表、反彙編等信息)
  • -c:對代碼進行反彙編

如:

javap -c xxx.class

javap -verbose Test.class

更多關於javap的介紹:javap - The Java Class File Disassembler

關於反彙編: 反彙編(Disassembly):把目標代碼轉爲彙編代碼的過程,也能夠說是把機器語言轉換爲彙編語言代碼、低級轉高級的意思。軟件一切神祕的運行機制全在反彙編代碼裏面。 -- 來源:反彙編

二、Class文件解讀

JVM規範中的Class文件解讀

這一小節比較枯燥,大部分是文字描述,可是很重要,決定了咱們能不能正確把class二進制文件給解析出來,因此仍是須要先大體瞭解下。

JVM規範中 4.1. The ClassFile Structure 給咱們提供了一下的Class文件結構:

ClassFile {
    u4             magic; // 魔數
    u2             minor_version; // 副版本號
    u2             major_version; // 主版本號
    u2             constant_pool_count; // 常量池計數器
    cp_info        constant_pool[constant_pool_count-1]; // 常量池數據區
    u2             access_flags; // 訪問標誌
    u2             this_class; // 類索引
    u2             super_class; // 父類索引
    u2             interfaces_count; // 接口計數器
    u2             interfaces[interfaces_count]; // 接口表
    u2             fields_count; // 字段計數器
    field_info     fields[fields_count]; // 字段表
    u2             methods_count; // 方法計數器
    method_info    methods[methods_count]; // 方法表
    u2             attributes_count; // 屬性計數器
    attribute_info attributes[attributes_count]; // 屬性表
}
複製代碼

Class文件是一組以8位字節爲基礎單位的二進制流。以上類結構只有兩種數據類型:

  • 無符號數:無符號數屬於基本屬性類型,用u1, u2, u4, u8分別表明1個字節,2個字節,4個字節和8個字節的無符號數,能夠用它描述數字、索引引用、數量值或者utf8編碼的字符串值;
  • :由多個無符號數或者其餘表做爲數據項構成的複合數據類型,以命名_info結尾。

根據以上的Class文件結構,咱們能夠梳理出如下的Class文件結構圖:

image-20200201194731846

2.一、魔數 magic

用於標識這個文件的格式,Class文件格式的魔數爲 0xCAFEBABE

2.二、副版本號 minor_version,主版本號 major_version

minor_version和major_version項目的值是此類文件的次要版本號和主要版本號。 主版本號和次版本號共同決定了類文件格式的版本。 若是類文件的主版本號爲M,次版本號爲m,則將其類文件格式的版本表示爲M.m。 所以,能夠按字典順序對類文件格式版本進行排序,例如1.5 <2.0 <2.1。

2.三、常量池計數器 constant_pool_count

常量池描述着整個Class文件中全部的字面量信息。常量池計數器(constant_pool_count)的值等於常量池(constant_pool)表中的條目數加一。

若是constant_pool索引大於零且小於constant_pool_count,則該索引被視爲有效。

2.四、常量池表 constant_pool[]

constant_pool[]是一個結構表,表示各類字符串常量類和接口名稱字段名稱以及在ClassFile結構及其子結構中引用的其餘常量。 每一個constant_pool表條目的格式由其第一個「標籤」字節指示。

全部類型的常量池表項目有如下通用的格式:

cp_info {
    u1 tag;
    u1 info[];
}
複製代碼

constant_pool表的索引從1到constant_pool_count-1。

常量池中的14種常量結構

參考JVM規範:4.4. The Constant Pool,得出常量池各類常量類型的結構:

常量名稱 類型 項目 Description
CONSTANT_Class_info u1 tag 7:類或接口的符號引用
u2 name_index 指向全限定名常量項的索引
CONSTANT_Fieldref_info u1 tag 9:字段的符號引用
u2 class_index 指向聲明字段的類或者接口描述符CONSTANT_Class_info的索引項
u2 name_and_type_index 指向字段描述符CONSTANT_NameAndType的索引項
CONSTANT_Methodref_info u1 tag 10:類中方法的符號引用
u2 class_index 指向聲明方法的類描述符CONSTANT_Class_info的索引項
u2 name_and_type_index 指向名稱及類型描述符CONSTANT_NameAndType的索引項
CONSTANT_InterfaceMethodref_info u1 tag 11:接口中方法的符號引用
u2 class_index 指向聲明方法的接口描述符CONSTANT_Class_info的索引項
u2 name_and_type_index 指向名稱及類型描述符CONSTANT_NameAndType的索引項
CONSTANT_String_info u1 tag 8:字符串類型字面量
u2 string_index 指向字符串字面量的索引
CONSTANT_Integer_info u1 tag 3:整型字面量
u4 bytes 按照高位在前存儲的int值
CONSTANT_Float_info u1 tag 4:浮點型字面量
u4 bytes 按照高位在前存儲的float值
CONSTANT_Long_info u1 tag 5:長整型字面量
u4 high_bytes
u4 low_bytes ((long) high_bytes << 32) + low_bytes
CONSTANT_Double_info u1 tag 6:雙精度浮點型字面量
u4 high_bytes
u4 low_bytes
CONSTANT_NameAndType_info u1 tag 12:字段或方法的部分符號引用
u2 name_index 指向該字段或方法名稱常量項的索引
u2 descriptor_index 指向該字段或方法描述符常量項的索引
CONSTANT_Utf8_info u1 tag 1:UTF-8編碼的字符串
u2 length UTF-8編碼的字符串佔用的字節數
u1 byte[length] 長度爲length的UTF-8編碼的字符串
CONSTANT_MethodHandle_info u1 tag 15:表示方法句柄
u1 reference_kind 值必須在1到9的範圍內。該值表示此方法句柄的類型,該句柄表徵其字節碼行爲(§5.4.3.5)
u2 reference_index 值必須是對常量池的有效引用
CONSTANT_MethodType_info u1 tag 16:表示方法類型
u2 descriptor_index 值必須是指向constant_pool表的有效索引。該索引處的constant_pool條目必須是表明方法描述符的CONSTANT_Utf8_info結構
CONSTANT_InvokeDynamic_info u1 tag 18:表示一個動態方法調用點
u2 bootstrap_method_attr_index 值必須是此類文件的bootstrap方法表(§4.7.23)的bootstrap_methods數組的有效索引。
u2 name_and_type_index 值必須是指向constant_pool表的有效索引。該索引處的constant_pool條目必須是表明方法名稱和方法描述符(§4.3.3)的CONSTANT_NameAndType_info結構(§4.4.6)。

2.五、訪問標記 access_flags

access_flags是一種掩碼標誌,用於表示對該類或接口的訪問權限。每一個標誌的解釋以下:

標誌名稱 含義
ACC_PUBLIC 0x0001 標記爲 public,能夠被類外訪問。
ACC_FINAL 0x0010 標記定義爲 final,不容許有子類。
ACC_SUPER 0x0020 當調用到 invokespecial 指令時,須要特殊處理的父類方法。
ACC_INTERFACE 0x0200 是一個接口。
ACC_ABSTRACT 0x0400 是一個抽象類,不可以被實例化。
ACC_SYNTHETIC 0x1000 標記是由編譯器產生的,不存在於源碼中。
ACC_ANNOTATION 0x2000 標記爲註解類型。
ACC_ENUM 0x4000 標記爲枚舉類型。

注意:

  • 接口經過標記位ACC_INTERFACE來區分,若是不是這個標記位,則表示一個類,而非接口;
  • 設置了ACC_INTERFACE標記位,那麼ACC_ABSTRACT標記位也得設置,而且不得設置ACC_FINALACC_SUPER以及ACC_ENUM
  • 若是設置的不是ACC_INTERFACE,那麼除了ACC_ANNOTATION,其餘均可以設置。不能同時使用ACC_FINALACC_ABSTRACT標記;
  • 目前的Java虛擬機指令集的編譯器應設置ACC_SUPER標記。 在Java SE 8和更高版本中,Java虛擬機將在每一個類文件中設置ACC_SUPER標記,而無論該標誌在該類文件中的實際值和類文件的版本如何。ACC_SUPER標誌是爲了兼容舊版本編譯器編譯的代碼。在JDK1.0.2以前編譯生成Class沒有ACC_SUPER標誌,JDK1.0.2前的JVM遇到該標記將忽略它;
  • ACC_SYNTHETIC標記代表該類或者接口是由編譯器編譯產生,不存在與源碼中;
  • 註解類型必須設置ACC_ANNOTATION標記,具備ACC_ANNOTATION標記的同時,必需要有ACC_INTERFACE標記;
  • ACC_ENUM標記代表該類或其超類被聲明爲枚舉類型。

2.六、類索引 this_class

類索引的值必須是constant_pool表中的有效索引。該索引處的constant_pool條目必須是CONSTANT_Class_info結構,該結構表示此類文件定義的類或接口。

2.七、父類索引 super_class

對於一個類,父類索引的值必須爲零或必須是constant_pool表中的有效索引。 若是super_class項的值非零,則該索引處的constant_pool條目必須是CONSTANT_Class_info結構,該結構表示此類文件定義的類的直接超類。 直接超類或其任何超類都不能在其ClassFile結構的access_flags項中設置ACC_FINAL標誌。

若是super_class項的值爲零,則該類只多是java.lang.Object,這是沒有直接超類的惟一類或接口。

對於接口,父類索引的值必須始終是constant_pool表中的有效索引。該索引處的constant_pool條目必須是java.lang.Object的CONSTANT_Class_info結構。

2.八、接口計數器 interfaces_count

接口計數器表示當前類或接口類型的直接超接口的數量。

2.九、接口表 interfaces[]

接口表的每一個值都必須是constant_pool表中的有效索引。interfaces [i]的每一個值(其中0≤i <interfaces_count)上的constant_pool條目必須是CONSTANT_Class_info結構,該結構描述當前類或接口類型的直接超接口。

2.十、字段計數器 fields_count

字段計數器的值給出了fields表field_info(§4.5)結構的數量。 field_info結構表明此類或接口類型聲明的全部字段,包括類變量和實例變量。

2.十一、字段表 fields[]

字段表中的每一個值都必須是field_info結構(§4.5),以提供對該類或接口中字段的完整描述。 字段表僅包含此類或接口聲明的字段,不包含從超類或超接口繼承的字段。

字段有以下結構:

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
複製代碼

2.十二、方法計數器 methods_count

方法計數器的值表示方法表中method_info§4.6結構的數量。

2.1三、方法表 methods[]

方法表中的每一個值都必須是method_info§4.6結構,以提供對該類或接口中方法的完整描述。 若是在method_info結構的access_flags項中均未設置ACC_NATIVEACC_ABSTRACT標誌,則還將提供實現該方法的Java虛擬機指令;

method_info結構表示此類或接口類型聲明的全部方法,包括實例方法,類方法,實例初始化方法以及任何類或接口初始化的方法。 方法表不包含表示從超類或超接口繼承的方法。

方法具備以下結構:

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
複製代碼

其中

2.1四、屬性計數器 attributes_count

屬性計數器的值表示當前類的屬性表中的屬性數量。

2.1五、屬性表 attributes[]

注意,這裏的屬性並非Java代碼裏面的類屬性(類字段),而是Java源文件便已有特有的一些屬性,參考如下表格。

  • 屬性表的每一個值都必須是attribute_info (§4.7)結構,屬性的通用結構以下:

    • attribute_info {
          u2 attribute_name_index;
          u4 attribute_length;
          u1 info[attribute_length];
      }
      複製代碼
    • 不一樣的屬性有不一樣的info[]

  • 在Java 8 規範中,ClassFile結構中的的屬性表中的屬性包括:

Attribute Location class file
SourceFile ClassFile 45.3
InnerClasses ClassFile 45.3
EnclosingMethod ClassFile 49.0
SourceDebugExtension ClassFile 49.0
BootstrapMethods ClassFile 51.0
ConstantValue field_info 45.3
Code method_info 45.3
Exceptions method_info 45.3
RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations method_info 49.0
AnnotationDefault method_info 49.0
MethodParameters method_info 52.0
Synthetic ClassFile, field_info, method_info 45.3
Deprecated ClassFile, field_info, method_info 45.3
Signature ClassFile, field_info, method_info 49.0
RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations ClassFile, field_info, method_info 49.0
LineNumberTable Code 45.3
LocalVariableTable Code 45.3
LocalVariableTypeTable Code 49.0
StackMapTable Code 50.0
RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations ClassFile, field_info, method_info, Code 52.0
  • 如下給出了有關定義爲出如今ClassFile結構的屬性表中的屬性的規則:§4.7

  • 如下給出了有關ClassFile結構的屬性表中非預約義屬性的規則:§4.7.1

三、解析Class文件實例

接下來給你們展現一下Class文件。有以下Java文件:

package com.itzhai.classes;

public class TestA implements TestIntf{

    private int a = 0;

    @Override
    public void init(String title) {
        String tmp = "test method";
        a = 1;
    }

    public int getA() {
        return a;
    }

}

interface TestIntf {

    void init(String title);

}
複製代碼

3.一、分析class十六進制文件

編譯成Class文件以後,使用vim以十六進制打開:

00000000: cafe babe 0000 0034 001f 0a00 0500 1909  .......4........
00000010: 0004 001a 0800 1b07 001c 0700 1d07 001e  ................
00000020: 0100 0161 0100 0149 0100 063c 696e 6974  ...a...I...<init
00000030: 3e01 0003 2829 5601 0004 436f 6465 0100  >...()V...Code..
00000040: 0f4c 696e 654e 756d 6265 7254 6162 6c65  .LineNumberTable
00000050: 0100 124c 6f63 616c 5661 7269 6162 6c65  ...LocalVariable
00000060: 5461 626c 6501 0004 7468 6973 0100 1a4c  Table...this...L
00000070: 636f 6d2f 6974 7a68 6169 2f63 6c61 7373  com/itzhai/class
00000080: 6573 2f54 6573 7441 3b01 0004 696e 6974  es/TestA;...init
00000090: 0100 1528 4c6a 6176 612f 6c61 6e67 2f53  ...(Ljava/lang/S
000000a0: 7472 696e 673b 2956 0100 0574 6974 6c65  tring;)V...title
000000b0: 0100 124c 6a61 7661 2f6c 616e 672f 5374  ...Ljava/lang/St
000000c0: 7269 6e67 3b01 0003 746d 7001 0004 6765  ring;...tmp...ge
000000d0: 7441 0100 0328 2949 0100 0a53 6f75 7263  tA...()I...Sourc
000000e0: 6546 696c 6501 000a 5465 7374 412e 6a61  eFile...TestA.ja
000000f0: 7661 0c00 0900 0a0c 0007 0008 0100 0b74  va.............t
00000100: 6573 7420 6d65 7468 6f64 0100 1863 6f6d  est method...com
00000110: 2f69 747a 6861 692f 636c 6173 7365 732f  /itzhai/classes/
00000120: 5465 7374 4101 0010 6a61 7661 2f6c 616e  TestA...java/lan
00000130: 672f 4f62 6a65 6374 0100 1b63 6f6d 2f69  g/Object...com/i
00000140: 747a 6861 692f 636c 6173 7365 732f 5465  tzhai/classes/Te
00000150: 7374 496e 7466 0021 0004 0005 0001 0006  stIntf.!........
00000160: 0001 0002 0007 0008 0000 0003 0001 0009  ................
00000170: 000a 0001 000b 0000 0038 0002 0001 0000  .........8......
00000180: 000a 2ab7 0001 2a03 b500 02b1 0000 0002  ..*...*.........
00000190: 000c 0000 000a 0002 0000 0003 0004 0005  ................
000001a0: 000d 0000 000c 0001 0000 000a 000e 000f  ................
000001b0: 0000 0001 0010 0011 0001 000b 0000 004f  ...............O
000001c0: 0002 0003 0000 0009 1203 4d2a 04b5 0002  ..........M*....
000001d0: b100 0000 0200 0c00 0000 0e00 0300 0000  ................
000001e0: 0900 0300 0a00 0800 0b00 0d00 0000 2000  .............. .
000001f0: 0300 0000 0900 0e00 0f00 0000 0000 0900  ................
00000200: 1200 1300 0100 0300 0600 1400 1300 0200  ................
00000210: 0100 1500 1600 0100 0b00 0000 2f00 0100  ............/...
00000220: 0100 0000 052a b400 02ac 0000 0002 000c  .....*..........
00000230: 0000 0006 0001 0000 000e 000d 0000 000c  ................
00000240: 0001 0000 0005 000e 000f 0000 0001 0017  ................
00000250: 0000 0002 0018 0a                        .......
複製代碼

根據JVM規範的介紹,咱們來解析下這個十六進制文件:

image-20191229214609050

image-20191229214627178

能夠發現,十六進制文件分析後,獲得的結果跟文章開頭的Class文件結構圖徹底對的上。

3.二、反彙編Class字節碼文件

使用javap -v輸出附加信息,能夠看到本地變量表,常量池,異常表,代碼偏移量映射表等信息。

反彙編以後的內容,跟JVM中描述的Class規範中的概念基本一致,對照JVM Class文件規範,便可解讀反彙編後的Class字節碼文件。參考:Chapter 4. The class File Format

同時,爲了解讀如下字節碼指令,您須要提早了解相關指令做用。

The Java Virtual Machine Instruction Set

Java bytecode instruction listings

Introduction to Java Bytecode

如下是反彙編Class字節碼獲得的內容:

Classfile /Users/arthinking/Dev/demos/spring-demo/target/classes/com/itzhai/classes/TestA.class
  Last modified Dec 29, 2019; size 598 bytes
  MD5 checksum b8a7dcaba9f5ddeb930aec319bc3ad16
  Compiled from "TestA.java"
public class com.itzhai.classes.TestA implements com.itzhai.classes.TestIntf
  // 副版本號
  minor version: 0
  // 主版本號
  major version: 52
  // 訪問標記
  flags: ACC_PUBLIC, ACC_SUPER
// 常量池(類中全部的字面量信息都在這裏)
Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."<init>":()V
   #2 = Fieldref           #4.#26         // com/itzhai/classes/TestA.a:I
   #3 = String             #27            // test method
   #4 = Class              #28            // com/itzhai/classes/TestA
   #5 = Class              #29            // java/lang/Object
   #6 = Class              #30            // com/itzhai/classes/TestIntf
   #7 = Utf8               a
   #8 = Utf8               I
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/itzhai/classes/TestA;
  #16 = Utf8               init
  #17 = Utf8               (Ljava/lang/String;)V
  #18 = Utf8               title
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               tmp
  #21 = Utf8               getA
  #22 = Utf8               ()I
  #23 = Utf8               SourceFile
  #24 = Utf8               TestA.java
  #25 = NameAndType        #9:#10         // "<init>":()V
  #26 = NameAndType        #7:#8          // a:I
  #27 = Utf8               test method
  #28 = Utf8               com/itzhai/classes/TestA
  #29 = Utf8               java/lang/Object
  #30 = Utf8               com/itzhai/classes/TestIntf
{
  // 默認構造方法,完成類的初始化(成員變量初始化賦值等)
  public com.itzhai.classes.TestA();
    descriptor: ()V
    // 訪問標記
    flags: ACC_PUBLIC
    // 方法的Code屬性,格式參考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
    Code:
      // 操做數棧最大大小=2,本地變量數量=1
      stack=2, locals=1, args_size=1
         0: aload_0 // 從本地變量表中加載第0項,即下面本地變量表中的this,入棧
         1: invokespecial #1 // 出棧,調用Method java/lang/Object."<init>":()V 初始化對象
         4: aload_0 // this引用入棧
         5: iconst_0 // 將常量0壓入到操做數棧
         6: putfield      #2 // Field a:I 將0取出,賦值給a
         9: return
      // 指令與代碼行數的偏移對應關係,第一個數字問代碼行數,第二個數字爲上面Code中指令前面的數字
      LineNumberTable:
        line 3: 0
        line 5: 4
      // 本地變量表
      // start和length分表表示這個本地變量在字節碼中的生命週期開始的字節碼偏移量及其做用域範圍覆蓋的長度。二者結合起來就是這個本地變量在字節碼中的做用域範圍。slot就是這個變量在本地變量表中的槽位(槽位可複用),name就是變量名稱,Signature是本地變量類型描述
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/itzhai/classes/TestA;

  // 類中自定義的init方法
  public void init(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    // 訪問標記
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: ldc           #3                  // String test method 將常量」test method「的在常量池中的索引壓入棧
         2: astore_2  // 從棧中取出剛剛的索引,存儲到本地變量表的tmp中
         3: aload_0 // this引用入棧
         4: iconst_1 // 數值1入棧
         5: putfield      #2                  // Field a:I  數值1出棧,賦值給 常量池#2,這裏是一個Fieldref,對應代碼中的a變量
         8: return
      LineNumberTable:
        line 9: 0
        line 10: 3
        line 11: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/itzhai/classes/TestA;
            0       9     1 title   Ljava/lang/String;
            3       6     2   tmp   Ljava/lang/String;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0 // this引用入棧
         1: getfield      #2                  // Field a:I 從常量池#2中取值,這裏是變量a
         4: ireturn // 返回整型結果,即a的值
      LineNumberTable:
        line 14: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/itzhai/classes/TestA;
}
SourceFile: "TestA.java"
複製代碼

四、JVM堆棧工做原理

有了以上案例以後,咱們如今來梳理下JVM堆棧的工做原理。

Java運行時數據區域如何工做這邊文章中,咱們已經大體瞭解了Java運行數據區的工做原理,並提供了下圖:

image-20191230225433436

下面咱們把這個圖放大,詳細瞭解一下虛擬機棧幀中,本地變量表和操做數棧的工做原理圖。

咱們知道,每一個方法調用都對應一個棧幀,如今咱們把其中一個棧幀放大:

image-20191230230617261

如上圖,這裏咱們重點關注本地變量表和操做數棧的工做。

  • 本地變量表:本地變量表長度編譯期肯定,一個本地變量(Slot)能夠存32位之內的數據,能夠保存類型爲 int, short, reference, byte, char, floath和returnAddress的數據,兩個本地變量能夠保存類型爲long和double的數據;
  • 操做數棧:每一個棧幀內部都包含一個稱爲操做數棧的後進先出棧,提供給方法計算過程使用。

以上面例子中的自定義的init方法爲例,說明下本地變量表和操做數棧的工做原理:

@Override
    public void init(String title) {
        String tmp = "test method";
        a = 1;
    }
複製代碼

對應的彙編指令:

// 類中自定義的init方法
  public void init(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    // 訪問標記
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: ldc           #3 // String test method 將常量」test method「的在常量池中的索引壓入棧
         2: astore_2  // 從棧中取出剛剛的索引,存儲到本地變量表的tmp中
         3: aload_0 // this引用入棧
         4: iconst_1 // 數值1入棧
         5: putfield      #2 // Field a:I 數值1出棧,賦值給 常量池#2,這裏是一個Fieldref,對應代碼中的a變量
         8: return
      LineNumberTable:
        line 9: 0
        line 10: 3
        line 11: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/itzhai/classes/TestA;
            0       9     1 title   Ljava/lang/String;
            3       6     2   tmp   Ljava/lang/String;
複製代碼

如上面的彙編指令,能夠知道,操做數棧的最大大小爲2,本地變量表的size=3,得出以下結構:

image-20191231000845790

其中b本地變量第0項爲TestA實例的地址在堆中的地址引用。

接下來解讀下後續的指令操做。

0: ldc #3

從字符串常量池中獲取」test method「的應用,並壓入操做數棧中:

image-20191231002818921

2: astore_2

從操做數棧中取出剛剛的索引,存儲到本地變量表的tmp中:

image-20191231002949129

3: aload_0

this引用入棧:

image-20191231003103293

4: iconst_1

數值1入棧:

image-20191231003129523

5: putfield #2

數值1和this引用出棧,把數值1賦值給this引用對應的實例的屬性a。這裏的#2是從常量池中獲取到一個Fieldref,對應代碼中的a變量。

8: return

方法返回void。

References

Java Virtual Machine Specification

Introduction to Java Bytecode

《深刻理解Java虛擬機-JVM高級特性與最佳實踐》

反彙編

javap - The Java Class File Disassembler

Introduction to Java Bytecode


本文爲arthinking基於相關技術資料和官方文檔撰寫而成,確保內容的準確性,若是你發現了有何錯漏之處,煩請高擡貴手幫忙指正,萬分感激。

你們能夠關注個人博客:itzhai.com 獲取更多文章,我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網絡編程、數據結構、數據庫、算法、併發編程、分佈式系統等相關內容。

若是您以爲讀完本文有所收穫的話,能夠關注個人帳號,或者點贊啥的。關注個人公衆號,及時獲取最新的文章。


本文做者: arthinking

博客連接: www.itzhai.com/jvm/the-sec…

Class文件十六進制背後的祕密

版權聲明: BY-NC-SA許可協議:創做不易,如需轉載,請務必附加上博客連接,謝謝!


相關文章
相關標籤/搜索