class文件屬性表解析

class文件結(jié)構(gòu)解析一文中,我們介紹了class文件的構(gòu)成床绪,整個class文件一共包含3部分共16個屬性:

  • 3個描述文件屬性的數(shù)據(jù)項:魔數(shù)和主次版本號
  • 11個描述類屬性的數(shù)據(jù)項:類、字段歧蕉、方法等信息
  • 2個描述代碼屬性的數(shù)據(jù)項:屬性表,描述方法體內(nèi)的具體內(nèi)容

其中文件屬性和類屬性在上一篇中已經(jīng)有過介紹,本文將主要介紹一下屬性表珠洗。在最新的JVM規(guī)范中,一共定義了21個屬性若专,接下來我將對其中一些關(guān)鍵屬性進行分析许蓖。

對于每一個屬性,它的結(jié)構(gòu)可以分為3部分:

  • 一個u2類型的屬性名稱(attribute_name_index):從常量池中引用的一個常量
  • 一個u4類型的屬性長度(attribute_length):屬性值所占用的字節(jié)數(shù)
  • attribute_length個u1類型的屬性值:具體的屬性值
1. Code屬性

java源文件方法體中的代碼經(jīng)過編譯后调衰,最終存儲在Code屬性內(nèi)膊爪,它的結(jié)構(gòu)如下:

類型 名稱 數(shù)量
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_table_length
u2 attributes_count 1
attribute_info attributes attributes_count
  • attribute_name_index是一個指向常量池中某一個常量的索引,取值固定為Code
  • attribute_length表示屬性值的長度
  • max_stack表示操作數(shù)棧的最大深度嚎莉,jvm運行時會根據(jù)這個值來分配棧幀中的操作數(shù)棧深度
  • max_locals表示局部變量表所需要的存儲空間米酬,單位為slot
  • code_length代表字節(jié)碼指令長度
  • code代表具體的字節(jié)碼指令,根據(jù)jvm規(guī)范趋箩,每個字節(jié)碼指令占用一個字節(jié)赃额,jvm可以自動識別該指令是否需要接收參數(shù)。
  • exception_table_length表示異常表占用的字節(jié)數(shù)
  • exception_table表示具體的異常表
  • Code屬性本身還有自己的一些屬性表叫确,包括LineNumberTable跳芳、LocalVariableTable和StackMapTable,這些屬性不是必須的竹勉,如果有的話飞盆,會在attributes_count和attributes中體現(xiàn)出來
2. LineNumberTable屬性

LineNumberTable是Code屬性中的一個子屬性,用來描述java源文件行號與字節(jié)碼文件偏移量之間的對應關(guān)系次乓。當程序運行拋出異常時吓歇,異常堆棧中顯示出錯的行號就是根據(jù)這個對應關(guān)系來顯示的,它的結(jié)構(gòu)如下:

類型 名稱 數(shù)量
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

其中l(wèi)ine_number_info結(jié)構(gòu)如下:

類型 名稱 數(shù)量 含義
u2 start_pc 1 字節(jié)碼偏移量
u2 line_number 1 java源文件行號
3. LocalVariableTable屬性

LocalVariableTable也是Code屬性中的一個子屬性檬输,用來描述棧幀的局部變量表中變量與java源碼中變量的對應關(guān)系照瘾,其結(jié)構(gòu)如下:

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 llocal_variable_table_length 1
local_variable_info local_variable_table local_variable_table_length

其中l(wèi)ocal_variable_info結(jié)構(gòu)如下:

類型 名稱 數(shù)量 含義
u2 start_pc 1 變量生命周期開始時的字節(jié)碼偏移量
u2 length 1 變量作用范圍覆蓋的字節(jié)數(shù)
u2 name_index 1 索引值,指向變量名稱
u2 descriptor_index 1 索引值丧慈,指向變量描述符
u2 index 1 變量在棧幀中slot的位置

LineNumberTable和LocalVariableTable都不是運行時必須的析命,可以在javac中使用-g:none選項來取消生成這兩個屬性,取消前后反編譯出來的文件將丟失這兩個屬性逃默,如下圖所示:

默認條件鹃愤,有屬性信息
取消后,信息丟失
4. 實例分析

我們還是繼續(xù)以上一篇中的代碼為例進行分析:
java源文件:

java源文件.png

利用javap得到的字節(jié)碼內(nèi)容(這里只給出了main方法的):

// 這一部分是描述方法的元數(shù)據(jù)完域,在上一篇已經(jīng)分析過
public static void main(java.lang.String[]);
  descriptor: ([Ljava/lang/String;)V
  flags: ACC_PUBLIC, ACC_STATIC

// 這一部分描述方法體的具體內(nèi)容
  Code:

    // 操作數(shù)棧最大深度為3软吐,局部變量表最多需要一個slot,有一個入?yún)?    // 如果是實例方法吟税,還會增加一個this對象的引用作為隱形參數(shù)
    stack=3, locals=1, args_size=1

       // 具體的字節(jié)碼指令凹耙,共占用28個字節(jié)
       // 獲取System.out屬性值姿现,壓入棧頂
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       // 創(chuàng)建StringBuilder實例,并將其引用壓入棧頂
       3: new           #3                  // class java/lang/StringBuilder
       // 復制棧頂元素肖抱,并壓入棧頂
       6: dup
       // 調(diào)用init()方法备典,彈出棧頂元素
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
       // 加載常量Hello,壓入棧頂
      10: ldc           #5                  // String Hello
       // 調(diào)用StringBuilder的append()方法
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       // 獲取name屬性值
      15: getstatic     #7                  // Field name:Ljava/lang/String;
       // 調(diào)用StringBuilder的append()方法
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       // 調(diào)用StringBuilder的toString()方法
      21: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       // 調(diào)用PrintStream的println()方法
      24: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return

    // java源碼行號與字節(jié)碼文件偏移量的對應關(guān)系
    LineNumberTable:
      line 14: 0
      line 15: 27

    // 棧幀中局部變量表變量與java源碼變量的對應關(guān)系
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      28     0  args   [Ljava/lang/String;

再來看看main方法code屬性對應的十六進制文件意述,按照code屬性結(jié)構(gòu)對其進行分析提佣,可以發(fā)現(xiàn)其內(nèi)容與javap得到的結(jié)果完全一致。

main方法的Code屬性.png

main方法里只看到了加載常量Hello的操作荤崇,那么有人可能會問拌屏,靜態(tài)常量name的屬性值是在哪里加載的呢?實際上术荤,這一步在cinit()方法中就完成了倚喂。

static {};
  descriptor: ()V
  flags: ACC_STATIC
  Code:
    // 類方法,不會傳入this引用瓣戚,故args_size=0
    stack=1, locals=0, args_size=0
       // 從常量池加載靜態(tài)常量务唐,壓入棧頂
       0: ldc           #10                 // String JVM
       // 為靜態(tài)屬性name賦值
       2: putstatic     #7                  // Field name:Ljava/lang/String;
       5: return
    LineNumberTable:
      line 11: 0

通過查看方法的字節(jié)碼,可以更直觀的看出方法內(nèi)部的執(zhí)行邏輯带兜。比如說對于字符串連接操作:

 "Hello " + name;

底層實際上是通過新建一個StringBuilder對象來實現(xiàn)的:

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(name);
sb.toString();

事實上,很多問題都可以由此迎刃而解吨灭,比如說i++和++i到底有什么區(qū)別刚照。以下4段代碼,最終i的值分別是多少呢喧兄?稍有經(jīng)驗的程序員都可以輕松給出答案无畔,但是其實現(xiàn)原理是什么呢,我們不妨從字節(jié)碼角度來略探一二吠冤。

    public static void incr1() {
        int i = 0;
        i = i++;
    }

    public static void incr2() {
        int i = 0;
        i++;
    }

    public static void incr3() {
        int i = 0;
        i = ++i;
    }

    public static void incr4() {
        int i = 0;
        ++i;
    }
  public static void incr1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iload_0
         3: iinc          0, 1
         6: istore_0
         7: return
      LineNumberTable:
        line 20: 0
        line 21: 2
        line 22: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2       6     0     i   I

  public static void incr2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iinc          0, 1
         5: return
      LineNumberTable:
        line 26: 0
        line 27: 2
        line 28: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2       4     0     i   I

  public static void incr3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iinc          0, 1
         5: iload_0
         6: istore_0
         7: return
      LineNumberTable:
        line 32: 0
        line 33: 2
        line 34: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2       6     0     i   I

  public static void incr4();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iinc          0, 1
         5: return
      LineNumberTable:
        line 38: 0
        line 39: 2
        line 40: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2       4     0     i   I

具體分析可以參考占小狼的文章從字節(jié)碼角度分析 i++ 和 ++i 實現(xiàn)浑彰,圖文并茂,清晰易懂拯辙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郭变,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涯保,更是在濱河造成了極大的恐慌诉濒,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夕春,死亡現(xiàn)場離奇詭異未荒,居然都是意外死亡,警方通過查閱死者的電腦和手機及志,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門片排,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寨腔,“玉大人,你說我怎么就攤上這事率寡∑嚷” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵勇劣,是天一觀的道長靖避。 經(jīng)常有香客問我,道長比默,這世上最難降的妖魔是什么幻捏? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮命咐,結(jié)果婚禮上篡九,老公的妹妹穿的比我還像新娘。我一直安慰自己醋奠,他們只是感情好榛臼,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窜司,像睡著了一般禽翼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惰爬,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天循签,我揣著相機與錄音,去河邊找鬼议薪。 笑死尤蛮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的斯议。 我是一名探鬼主播产捞,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哼御!你這毒婦竟也來了坯临?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恋昼,失蹤者是張志新(化名)和其女友劉穎尿扯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焰雕,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡衷笋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辟宗。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡爵赵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泊脐,到底是詐尸還是另有隱情空幻,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布容客,位于F島的核電站秕铛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缩挑。R本人自食惡果不足惜但两,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望供置。 院中可真熱鬧谨湘,春花似錦、人聲如沸芥丧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽续担。三九已至擅耽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間物遇,已是汗流浹背秫筏。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挎挖,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓航夺,卻偏偏與公主長得像蕉朵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阳掐,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容