JVM字節碼文件概述

字節碼文件概述

字節碼文件的跨平臺性

Java語言:跨平臺的語言

  • 當Java源代碼成功編譯字節碼後,若是想在不一樣的平臺上面運行,則無需再次編譯
  • 這個優點目前來講已經再也不吸引人,由於Python、PHP、Ruby、Lisp等有強大的解釋器
  • 跨平臺已經快成爲一門語言的必選特性

Java虛擬機:跨語言的平臺

Java虛擬機不和包括Java在內的任何語言綁定,它只與Class文件這種特定的二進制文件所關聯,不管使用何種語言進行軟件開發,只要能將源文件編譯爲正確的Class文件,那麼這種語言就能夠這Java虛擬機上執行。前端

JVM的平臺無關性

想要讓一個Java程序正確的運行在JVM中,Java源碼就必需要被編譯爲符合JVM規範的字節碼。java

  • 前端編譯器的主要任務就是負責將符合Java語法規範的Java代碼轉換爲符合JVM規範的字節碼文件
  • javac是一種可以將Java源碼編譯爲字節碼的前端編譯器
  • javac編譯器這將Java源碼編譯爲一個有效的字節碼文件過程當中經歷了4個步驟,分別是詞法解析、語法解析、語義解析以及生成字節碼

JVM結構

Java的編譯

理解執行引擎

  1. 前端編譯面試

    Java源代碼的編譯結果是字節碼,那麼確定要有一種可以將Java源代碼編譯爲字節碼,承擔這個責任的就是一配置在path環境變量中的javac編譯器。javac是一種可以將Java源碼編譯爲字節碼的前端編譯器後端

    優勢:數組

    1. 許多Java語法新特性(泛型、內部類等),是靠前端編譯器實現的,而不是依賴虛擬機。
    2. 編譯成的Class文件能夠直接給JVM解釋器解釋執行,省去編譯時間,加快啓動速度。

缺點:安全

    1. 對代碼運行效率幾乎沒有任何優化措施。
    2. 解釋執行效率較低,因此須要結合下面的JIT編譯。
    1. 後端編譯/JIT編譯框架

      經過Java虛擬機(JVM)內置的即時編譯器(Just In Time Compiler,JIT編譯器);在運行時把Class文件字節碼編譯成本地機器碼的過程。佈局

      優勢:性能

      1. 經過在運行時收集監控信息,把"熱點代碼"(Hot Spot Code)編譯成與本地平臺相關的機器碼,並進行各類層次的優化。
      2. 能夠大大提升執行效率。

    缺點:優化

    1. 收集監控信息影響程序運行。
    2. 編譯過程佔用程序運行時間。
    3. 編譯機器碼佔用內存。
    1. 靜態提早編譯(AOT)

      程序運行前,直接把Java源碼文件編譯成本地機器碼的過程。

      優勢:

      1. 編譯不佔用運行時間,能夠作一些較耗時的優化,並可加快程序啓動。
      2. 把編譯的本地機器碼保存磁盤,不佔用內存,並可屢次使用。

    缺點:

    1. 由於Java語言的動態性(如反射)帶來了額外的複雜性,影響了靜態編譯代碼的質量,通常靜態編譯不如JIT編譯的質量,這種方式用得比較少。

    目前Java體系中主要仍是採用前端編譯+JIT編譯的方式

    運做過程:

    1. 首先經過前端編譯把符合Java語言規範的程序代碼轉化爲知足JVM規範所要求Class格式。
    2. 而後程序啓動時Class格式文件發揮做用,解釋執行,省去編譯時間,加快啓動速度。
    3. 針對Class解釋執行效率低的問題,在運行中收集性能監控信息,得知"熱點代碼"。
    4. JIT逐漸發揮做用,把愈來愈多的熱點代碼"編譯優化成本地代碼,提升執行效率。

    透過字節碼指令看代碼細節

    面試題

    1. 類文件結構有幾個部分?
    2. 知道字節碼嗎?字節碼都有哪些?Integer x = 5; int y = 5; 比較 x == y 都經歷哪些步驟?

    image

    首先在聲明Integer x的時候會調用Integer.valueOf方法,首先看下這個方法

    public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
    }

    若是所傳入的值是-128~127之間,就只用靜態內部類的cache數組,不然就new一個新的對象。

    這也代表爲何 Integer x = 128; Integer y = 128; x != y 的緣由。

    以後能夠看到調用了 Integer.intValue 進行自動拆箱操做,使得 x 變成 int 類型進行比較。

    虛擬機的基石:Class文件

    • 字節碼文件裏是什麼?

      源代碼通過編譯器編譯以後便生成一個字節碼文件,字節碼是一種二進制的類文件,它的內容是JVM的指令,而不像C、C++經由編譯器直接生成機器碼

    • 什麼是字節碼指令?

      Java虛擬機的指令,由一個字節長度的、表明着某種特定操做含義的操做碼(opcode),以及跟隨其後的零至多個表明此操做所需參數的操做數(operand)所構成。虛擬機中許多指令並不包含操做數,只有一個操做碼。

      好比 aload_1 (操做碼) 、aload 4 (操做碼 + 操做數),由於aload只有0、一、二、3因此若是想繼續擴充,就要用到操做數。

      image

    Class文件結構

    • Class類的本質

      因爲一個Class文件都對應着惟一一個類或接口的定義信息,但反過來講,Class文件實際上它並不必定以磁盤文件形式存在。Class文件是一組以8位字節爲基礎單位的二進制流

    • Class文件格式

      Class的結構不像 XML 等描述語言,因爲它沒有任何分割符號,因此這其中的數據項,不管是字節順序仍是數量,都是被嚴格限定的,哪一個字節表明什麼含義、長度多少、前後順序,都不容許改變。

      Class文件格式採用一種相似於C語言結構體的方式進行數據存儲,這種結構中只有兩種數據類型:無符號數

      • 無符號數屬於基本數據類型,以 u1 、u2 、u四、u8 來分別表明1個字節、2個字節、4個字節和8個字節的無符號數,無符號數能夠用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
      • 表是由多個無符號數或者其它表做爲數據項構成的複合數據類型,全部表都習慣性地以"_info"結尾。表用於描述有層次關係的複合結構的數據,整個Class文件本質就是一張表,因爲表沒有固定長度,因此一般會其前面加上個數說明。
    • Class文件結構概述

      Class文件的結構並非一成不變的,隨着Java虛擬機的不斷髮展,老是不可避免地會對Class文件結構作出一些調整,可是基本結構和框架是很是穩定的。

      結構以下:

      • 魔數
      • Class文件版本
      • 常量池
      • 訪問標誌
      • 類索引,父類索引,接口索引集合
      • 字段表集合
      • 方法表集合
      • 屬性表集合

    image

    | 類型 | 名稱 | 說明 | 長度 | 數量 |
    | -------------- | ------------------- | ---------------------- | ------- | --------------------- |
    | u4 | magic | 魔數,識別Class文件格式 | 4個字節 | 1 |
    | u2 | minor_version | 副版本號(小版本) | 2個字節 | 1 |
    | u2 | major_version | 主版本號(大版本) | 2個字節 | 1 |
    | u2 | constant_pool_count | 常量池計數器 | 2個字節 | 1 |
    | cp_info | constant_pool | 常量池表 | n個字節 | constant_pool_count-1 |
    | u2 | access_flags | 訪問標識 | 2個字節 | 1 |
    | u2 | this_class | 類索引 | 2個字節 | 1 |
    | u2 | super_class | 父類索引 | 2個字節 | 1 |
    | u2 | interfaces_count | 接口計數器 | 2個字節 | 1 |
    | u2 | interfaces | 接口索引集合 | 2個字節 | interfaces_count |
    | u2 | fields_count | 字段計數器 | 2個字節 | 1 |
    | field_info | fields | 字段表 | n個字節 | fields_count |
    | u2 | methods_count | 方法計數器 | 2個字節 | 1 |
    | method_info | methods | 方法表 | n個字節 | methods_count |
    | u2 | attributes_count | 屬性計數器 | 2個字節 | 1 |
    | attribute_info | attributes | 屬性表 | n個字節 | attributes_count |

    字節碼文件解析

    首先咱們建立一個簡單的源碼:

    public class Demo {
        private int num = 1;
    
        public int add() {
            num = num + 2;
            return num;
        }
    }

    經過 javac 命令編譯後能夠看到Class文件內容:

    public class Demo {
        private int num = 1;
    
        public Demo() {
        }
    
        public int add() {
            this.num += 2;
            return this.num;
        }
    }

    爲何編譯之後增長了無參構造器,以及this關鍵字,咱們一點點分析。

    以後咱們使用Notepad++,須要安裝一個HEX-Editor插件來打開這個Class文件就能夠看到下圖內容。

    image

    這裏就直接使用 excel 來清晰解釋具體內容。

    image

    魔數:Class文件的標誌

    • 每一個Class文件開頭的4個字節的無符號整數成爲魔數(Magic Number)
    • 它的惟一做用就是肯定這個文件是否爲一個能被虛擬機接受的有效合法的Class文件。即:魔數是Class文件的標識符。
    • 魔數值固定爲0xCAFEBABE
    • 若是一個Class文件不以0xCAFEBABE開頭,虛擬機在進行文件校驗的時候就會直接拋出ClassFormatError錯誤。
    • 使用魔數而不是擴展名來識別主要是基於安全方面考慮,由於文件擴展名是能夠隨意改變的。

      Class文件版本號

    • 緊接着魔數的 4 個字節是 Class文件的版本號。一樣也是4個字節。第5個和第6個字節所表明的的含義就是編譯的副版本號minor_version,而第7個和第8個字節就是編譯的主版本號major_version。
    • 它們共同構成了Class文件的格式版本號。譬如某個Class文件的主版本號爲M,副版本號爲m,那麼這個Class文件的格式版本號就是 M.m。
    • 版本號和Java編譯器的對應關係表以下:

      | 主版本(十進制) | 副版本(十進制) | 編譯器版本 |
      | ---------------- | ---------------- | ---------- |
      | 45 | 3 | 1.1 |
      | 46 | 0 | 1.2 |
      | 47 | 0 | 1.3 |
      | 48 | 0 | 1.4 |
      | 49 | 0 | 1.5 |
      | 50 | 0 | 1.6 |
      | 51 | 0 | 1.7 |
      | 52 | 0 | 1.8 |
      | 53 | 0 | 1.9 |
      | 54 | 0 | 1.10 |
      | 55 | 0 | 1.11 |

    • Java的版本號是從45開始的,JDK 1.1 以後的每一個JDK大版本發佈主版本號向上加1。
    • 不一樣版本的Java編譯器編譯的Class文件對應的版本是不同的。目前,高版本的Java虛擬機能夠執行低版本編譯器編譯的Class文件,可是低版本的Java虛擬機不能執行由高版本編譯器生成的Class文件。不然JVM會拋出java.lang.UnsupportedClassVersionError異常。

    常量池:存放全部常量

    • 常量池是Class文件中內容最爲豐富的區域之一。常量池對於Class文件中的字段和方法解析有着相當重要的做用。
    • 隨着Java虛擬機的不斷髮展,常量池的內容也日漸豐富。能夠說,常量池是整個Class文件的基石。
    • 在版本號以後,緊跟着的就是常量池的數量,以及若干個常量池表項。
    • 常量池中常量的數量是不固定的,因此須要一個u2類型的無符號數,表明着常量池容量計數器(constant_pool_count),與Java語言習慣不同的是,容量計數器是從1開始而不是0。
    • 常量池表項中,用於存放編譯期生成的各類字面量符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。

    常量池計數器

    • 因爲常量池的數量不固定,因此須要放置兩個字節來表示常量池容量計數器。
    • 常量池計數器(u2類型):從1開始,表示常量池中有多少項常量。即constant_pool_count = 1表示常量池中有0個常量池。
    • 咱們看剛纔舉例的Demo:其值爲0x0016,轉換爲十進制也就是22。

      image

      可是實際中只有21項常量,範圍是1-21。

      這裏的常量池把第0項空出來了,爲了知足後面某些指向常量池的索引值的數據在特定狀況下須要表達"不引用任何一個常量池項"的含義,這種狀況可使用索引0來表示。

    常量池表

    • constant_pool是一種表及結構,以1 ~ constant_pool_count - 1 爲索引。代表後面有多少個常量項。
    • 常量池主要存放放兩大類變量:字面量(Literal)符號引用(Symbolic Reference)
    • 它包含了Class文件結構及其子結構中引用的全部字符串常量、類、或接口名、字段名、和其它常量。常量池中的每一項都具備相同特徵。第1個字節做爲類型標記,用於肯定該項的格式,這個字節成爲tag byte (標記字節)。
    字面量和符號引用

    常量池主要存放放兩大類變量:字面量(Literal)符號引用(Symbolic Reference)

    常量 具體的常量
    字面量 文本字符串
    聲明爲final的常量值
    符號引用 類和接口的全限定名
    字段和名稱的描述符
    方法的名稱和描述符

    全限定名

    com/test/Demo這個就是類的全限定名,僅僅是把包名的"."替換成了"/",爲了使連續的多個全限定名之間不產生混淆,在使用時最後通常會加入一個";"表示全限定名結束。

    簡單名稱

    簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,上面例子中的類的add()方法,和num字段的簡單名稱分別是add和num。

    描述符

    描述符的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(boolean,byte,char,short,int,float,long,double)以及表明無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示:

    標誌符 含義
    B 基本數據類型byte
    C 基本數據類型char
    D 基本數據類型double
    F 基本數據類型float
    I 基本數據類型int
    J 基本數據類型long
    S 基本數據類型short
    Z 基本數據類型boolean
    V 表明void類型
    L 對象類型,好比:Ljava/lang/Object;
    [ 數組類型,表明一維數組。好比:double[][][] = [[[D
    public static void main(String[] args) {
            Object[] arr = new Object[10];
            System.out.println(arr);//[Ljava.lang.Object;@14ae5a5
    
            Long[][] longs = new Long[10][10];
            System.out.println(longs);//[[Ljava.lang.Long;@7f31245a
            
            int[][] ints = new int[10][10];
            System.out.println(ints);//[[I@7f31245a
    }

    須要注意的是,用描述符來描述方法的時候,先參數列表後返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號"()"內。

    虛擬機在加載Class文件時纔會進行動態連接,也就是說,Class文件中不會保存各個方法和字段的最終內存佈局信息,所以,這些字段和方法的符號引用不通過轉換是沒法直接被虛擬機使用的。當虛擬機運行時,須要從常量池中得到對應的符號引用,再在類加載過程當中的解析階段將其替換爲直接引用,並翻譯到具體的內存地址中

    • 符號引用:符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時無歧義地定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到了內存中。
    • 直接引用:直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存佈局相關的,同一個符號引用在不一樣虛擬機實例上翻譯出來通常不會相同。若是有了直接引用,則說明引用的目標一定存在內存之中。
    常量類型和結構
    類型 標誌(或標識) 描述
    CONSTANT_utf8_info 1 UTF-8編碼的字符串
    CONSTANT_Integer_info 3 整型字面量
    CONSTANT_Float_info 4 浮點型字面量
    CONSTANT_Long_info 5 長整型字面量
    CONSTANT_Double_info 6 雙精度浮點型字面量
    CONSTANT_Class_info 7 類或接口的符號引用
    CONSTANT_String_info 8 字符串類型字面量
    CONSTANT_Fieldref_info 9 字段的符號引用
    CONSTANT_Methodref_info 10 類中方法的符號引用
    CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
    CONSTANT_NameAndType_info 12 字段或方法的符號引用
    CONSTANT_MethodHandle_info 15 表示方法句柄
    CONSTANT_MethodType_info 16 標誌方法類型
    CONSTANT_InvokeDynamic_info 18 表示一個動態方法調用點

    image

    從上面的圖中能夠看到,雖然每一項的結構都不相同,可是它們有個共同點,就是每一項的第一個字節都是一個標誌位,標識這一項是哪一種類型的常量。

    訪問標記(access_flag)

    • 在常量池後,緊接着訪問標記,該標記使用兩個字節表示,用於識別一些類或者接口層次的訪問信息,包括:這個Class是類仍是接口;是否認義爲 public 類型;是否認義爲 abstract 類型;若是是類的話,是否被聲明爲 final 等,詳細說明以下:

      標誌名稱 標誌值 含義
      ACC_PUBLIC 0x0001 標誌爲public類型
      ACC_FINAL 0x0010 標誌被聲明爲final,只有類能夠設置
      ACC_SUPER 0x0020 標誌容許使用invokespecial字節碼指令的新語義,JDK1.0.2以後編譯出來的類的這個標誌默認爲真。(使用加強的方法調用父類方法)
      ACC_INTERFACE 0x0200 標誌這是一個接口
      ACC_ABSTRACT 0x0400 是否爲abstract類型,對於接口或者抽象類來講,次標誌值爲真,其餘類型爲假
      ACC_SYNTHETIC 0x1000 標誌此類並不是由用戶代碼產生(即:由編譯器產生的類,沒有源碼對應)
      ACC_ANNOTATION 0x2000 標誌這是一個註解
      ACC_ENUM 0x4000 標誌這是一個枚舉
    • 類的訪問權限一般爲 ACC_ 開頭的常量。
    • 每一種類型的表示都是經過設置訪問標記的32位中的特定位來實現的,好比若是是public final的類,則標記爲 ACC_PUBLIC | ACC_FINAL。
    • 使用ACC_SUPER可讓類更準確的定位到父類的方法super.method(),默認都是設置並使用這個標記。

    咱們能夠看到上面的Demo的字節碼對應的訪問標記是21,也就是對應表格中的 ACC_PUBLIC 和 ACC_SUPER 加起來就等於21。

    類索引、父類索引、接口索引集合

    長度 含義
    u2 this_class
    u2 super_class
    u2 interfaces_count
    u2 interfaces[interfaces_count]
    • 類索引用於肯定這個類的繼承關係。
    • 父類索引用於肯定這個類的父類全限定名,因爲Java語言不容許多重繼承,因此父類索引只有一個,除了Object 以外,全部的Java類都有父類,所以除了Object以外,全部Java類的父類索引都不爲0。
    • 接口索引集合就用來描述這個類實現了拿些接口,這些被實現的接口將按implements語句(若是當前類自己是一個接口,則應當是 extends 語句)後的接口順序從左到右排列在接口索引集合中。

    this_class(類索引)

    2 字節無符號整數,指向常量池的索引。它提供了類的全限定名,如com/test/Demo。this_class的值必須是常量池中某項的一個有效索引值。常量池在這個索引出的成員必須爲constant_class_info類型結構體,該結構表示這個Class文件所定義的類或者接口。

    super_class(父類索引)

    • 2字節無符號整數,指向常量池的索引。它提供了當前類的父類全限定名。若是咱們沒有繼承任何類,其默認繼承的是Java/lang/Object類。同時,因爲Java不支持多繼承,因此其父類只有一個。
    • super_class指向的父類不能是final類型。

    interfaces

    • 指向常量池索引集合,它提供了一個符號引用到全部已實現的接口。
    • 因爲一個類能夠實現多個接口,所以須要以數組形式保存多個接口的索引,表示接口的每一個索引也是一個指向常量池的Constant_Class(指向的是接口,並非類)。

    interfaces_count(接口計數器)

    interfaces_count 項的值表示當前類或者接口的直接超接口數量。

    interfaces[](接口索引集合)

    interfaces[]中每一個成員的值必須是對常量池表中某項索引的有效索引值,它的長度爲interfaces_count。每一個成員interfaces[i]必須爲constant_class_info結構,其中 0 <= i < interfaces_count。在interfaces[]中,各成員所表示的接口順序對應的源代碼中給定的接口順序(從左至右)同樣,即 interface[0]對應的是源代碼中最左邊的接口。

    字段表集合

    • 用於描述接口或類中聲明的變量。字段(field)包括類級變量以及實例變量,可是不包括方法內部、代碼塊內部聲明的局部變量。
    • 字段叫什麼名字,字段被定義爲何數據類型,這些都是沒法固定的。只能引用常量池中的常量來描述。
    • 它指向常量池索引集合,它描述了每一個字段的完整信息。好比字段的標識符、訪問修飾符(public、private或protected)、是類變量仍是實例變量(static修飾符)、是不是常量(final修飾符)等。

    注意:

    • 字段表集合中不會列出從父類或者實現的接口中繼承來的字段,但有可能列出本來Java代碼之中不存在的字段。譬如在內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
    • 在Java語言中字段是沒法重載的,兩個字段的數據類型,修飾符無論是否相同,都必須使用不同的名稱,可是對於字節碼來講,若是兩個字段的描述符不一致,那字段名重名也是合法的。

    fields_count(字段計數器)

    fields_count的值表示當前Class文件fields表的成員個數,用2個字節表示。

    fields表中每一個成員都是一個field_info結構,用於表示該類或接口所聲明的全部字段或者實例字段,不包括方法內部聲明的變量,也不包括從父類或父接口繼承的那些字段。

    fields[](字段表)

    • fields表中的每一個成員都必須是一個field_info結構的數據項,用於表示當前類或接口中某個字段的完整描述。
    • 一個字段的信息包括以下這些信息。這些信息中,各個修飾符都是布爾值,要麼有,要麼沒有。

      • 做用域
      • 實例變量仍是類變量
      • 是否final
      • 是否volatile
      • 是否序列化 transient 修飾
      • 字段數據類型(基本數據類型,對象,數組)
      • 字段名稱
    • 字段表結構
    類型 名稱 含義 數量
    u2 access_flags 訪問標誌 1
    u2 name_index 字段名索引 1
    u2 descriptor_index 描述符索引 1
    u2 attributes_count 屬性計數器 1
    attribute_info attributes 屬性集合 attributes_count

    字段表訪問標識

    咱們知道,一個字段能夠被各類關鍵字修飾,好比做用域修飾符、static修飾符、final修飾符、volatile修飾符等等。字段訪問標誌有以下這些:

    標誌名稱 標誌值 含義
    ACC_PUBLIC 0x0001 字段是否爲public
    ACC_PRIVATE 0x0002 字段是否爲private
    ACC_PROTECTED 0x0004 字段是否爲protected
    ACC_STATIC 0x0008 字段是否爲static
    ACC_FINAL 0x0010 字段是否爲final
    ACC_VOLATILE 0x0040 字段是否爲volatile
    ACC_TRANSTENT 0x0080 字段是否爲transient
    ACC_SYNCHETIC 0x1000 字段是否爲由編譯器自動產生
    ACC_ENUM 0x4000 字段是否爲enum

    字段名索引

    根據字段名索引的值,查詢常量池中的指定索引項便可。

    描述符索引

    描述符的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型及無返回值的void都是用大寫符來表示,對象使用字符L+全限定名錶示。

    屬性集合

    一個字段還可能擁有一些屬性,用於存儲更多的額外信息。好比初始化值、一些註釋信息等。屬性個數存放在attribute_count中,屬性具體內容存在attributes數組中。

    結構爲:

    ConstantValue_attribute{
        u2 attribute_name_index;
        u4 attribute_length;
        u2 constantvalue_index;
    }

    對於常量屬性而言,attribute_length的值恆爲2。

    image

    常量值索引所指向的 #8 其實就是對應int的值。

    image

    方法表集合

    methods:指向常量池索引集合,它完整描述了每一個方法的簽名。

    • 在字節碼文件中,每個method_info項都對應着一個類或者接口中的方法信息。好比方法的訪問修飾符,方法的返回值類型,以及方法的參數信息等。
    • 若是方法不是抽象的或者不是native的,那麼字節碼就會體現出來。
    • 一方面,methods表只描述當前類或接口中聲明的方法,不包括從父類或父接口繼承的方法。另外一方面,methods表有可能會出現由編譯器自動添加的方法,最典型的就是編譯器產生的方法信息,好比類或者接口初始化方法<clinit>()和實例初始化方法<init>()

    methodds_count(方法計數器)

    methods_count的值表示當前class文件methods表的成員個數。使用 2 個字節來表示。

    methods表中每一個成員都是一個method_info結構。

    methods[](方法表)

    • methods表中的每一個成員都必須是一個method_info結構,用於表示當前類或接口中某個方法的完整描述。若是某個method_info結構的access_flags項既沒有設置ACC_NATIVE標誌和ACC_ABSTRACT標誌,那麼該結構中也應該包含實現這個方法所用到的Java虛擬機指令。
    • method_info結構能夠表示類和接口中定義的全部方法,包括實例方法、類方法、實例初始化方法和類或接口初始化方法。
    • 方法表的結構實際和字段表是一致的。以下:
    類型 名稱 含義 數量
    u2 access_flags 訪問標誌 1
    u2 name_index 字段名索引 1
    u2 descriptor_index 描述符索引 1
    u2 attributes_count 屬性計數器 1
    attribute_info attributes 屬性集合 attributes_count

    屬性表集合

    方法表集合以後的屬性表集合,指的是class文件所攜帶的輔助信息,好比該Class文件的源文件的名稱,以及任何帶有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的註解。這類信息一般被用於Java虛擬機的驗證和運行,以及Java程序的調試。

    此外,字段表、方法表均可以有本身的屬性表。用於描述某些場景專有的信息。

    屬性表集合的限制沒有那麼嚴格,再也不要求各個屬性表具備嚴格的順序,而且只要不與已有的屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,但Java虛擬機運行時會忽略掉它不認識的屬性。

    屬性的通用格式

    屬性表的結構比較靈活,各類不一樣的屬性只要知足如下結構便可:

    類型 名稱 數量 含義
    u2 attribute_name_index 1 屬性名索引
    u4 attribute_length 1 屬性長度
    u1 info attribute_length 屬性表

    屬性類型

    屬性名稱 使用位置 含義
    Code 方法表 Java代碼編譯成的字節碼指令
    ConstantValue 字段表 final關鍵字定義的常量池
    Deprecated 類,方法,字段表 被聲明爲deprecated的方法和字段
    Exceptions 方法表 方法拋出的異常
    EnclosingMethod 類文件 僅當一個類爲局部類或者匿名類是才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法
    InnerClass 類文件 內部類列表
    LineNumberTable Code屬性 Java源碼的行號與字節碼指令的對應關係
    LocalVariableTable Code屬性 方法的局部變量描述
    StackMapTable Code屬性 JDK1.6中新增的屬性,供新的類型檢查檢驗器檢查和處理目標方法的局部變量和操做數有所須要的類是否匹配
    Signature 類,方法表,字段表 用於支持泛型狀況下的方法簽名
    SourceFile 類文件 記錄源文件名稱
    SourceDebugExtension 類文件 用於存儲額外的調試信息
    Synthetic 類,方法表,字段表 標誌方法或字段爲編譯器自動生成的
    LocalVariableTypeTable 使用特徵簽名代替描述符,是爲了引入泛型語法以後能描述泛型參數化類型而添加
    RuntimeVisibleAnnotations 類,方法表,字段表 爲動態註解提供支持
    RuntimeInvisibleAnnotations 表,方法表,字段表 用於指明哪些註解是運行時不可見的
    RuntimeVisibleParameterAnnotation 方法表 做用與RuntimeVisibleAnnotations屬性相似,只不過做用對象爲方法
    RuntimeInvisibleParameterAnnotation 方法表 做用與RuntimeInvisibleAnnotations屬性相似,做用對象哪一個爲方法參數
    AnnotationDefault 方法表 用於記錄註解類元素的默認值
    BootstrapMethods 類文件 用於保存invokeddynamic指令引用的引導方式限定符

    Code屬性

    Code屬性就是存放方法體裏面的代碼,像接口或者抽象方法,沒有具體的方法體,所以也就不會有Code屬性了。

    Code屬性表的結構:

    類型 名稱 數量 含義
    u2 attribute_name_index 1 屬性名索引
    u4 attribute_length 1 屬性長度
    u2 max_stack 1 操做數棧深度的最大值
    u2 max_locals 1 局部變量表所需的存續空間
    u4 code_length 1 字節碼指令的長度
    u1 code code_length 存儲字節碼指令
    u2 exception_table_length 1 異常表長度
    exception_info exception_table exception_length 異常表
    u2 attributes_count 1 屬性集合計數器
    attribute_info attributes attributes_count 屬性集合

    LineNumberTable屬性

    • LineNumberTable屬性是可選變長屬性,位於Code結構的屬性表
    • LineNumberTable屬性是用來描述Java源碼行號字節碼行號之間的對應關係。
    • start_pc,即字節碼行號;line_number,即Java源代碼的行號
    • 在Code屬性的屬性表中,LineNumberTable屬性能夠按照任意順序出現,此外,多個LineNumberTable屬性能夠共同表示一個行號在源文件中表示的內容,即LineNumberTable屬性不須要與源文件的行一一對應。

    LineNumberTable屬性表結構

    LineNumberTable_attribute {  
       u2 attribute_name_index;  
       u4 attribute_length;  
       u2 line_number_table_length;  
       {   u2 start_pc;  
           u2 line_number;  
       } line_number_table[line_number_table_length];  
    }
    類型 名稱 數量 含義
    u2 attribute_name_index 1 屬性名索引
    u4 attribute_length 1 屬性長度
    u2 line_number_table_length 1 行號表長度
    line_number_info line_number_table line_number_table_length 行號表

    SourceFile屬性

    類型 名稱 數量 含義
    u2 attribute_name_index 1 屬性名索引
    u4 attribute_length 1 屬性長度
    u2 sourcefile_index 1 源碼文件索引

    其長度老是固定的8個字節。

    image

    咱們看以前的Excel最後一位也能夠看到對應的就是源文件名。

    相關文章
    相關標籤/搜索