能看懂的字節碼-下

前言

上一篇看得懂的字節碼講了怎麼看字節碼,以及字節碼中的魔數、版本號和常量池部分,這篇文章接着字節碼順序往下將。請必定要看了上一篇以後再看這一篇,由於有絕對的邏輯關係,不看上一篇這篇基本看不懂。java

若是這篇文章中有我沒有講到怎麼出現的東西,(好比程序的代碼,javap工具,查看字節碼的工具等)就是我在上一篇文章中已經提到過的,能夠查看個人上一篇文章來找到答案。git

上一篇地址 能看懂的字節碼-上github


個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關能夠點個star,劍指offer題解(Java版),設計模式。能夠看個人github主頁,天天都在更新喲。spring

邀請您跟我一同完成 repo設計模式


文章大綱

訪問標誌

緊接着常量池後面的兩個字節(u2類型)就表示訪問的標誌,在字節碼中是這兩個數數組

那麼他表明什麼呢?他表示這個類或者接口聲明爲 publicide

咱們看他的全部含義的表 函數

21就是 ACC_PUBLIC 和 ACC_SUPER 這兩個標誌值相或的結果。即 0x0001 | 0x0020 = 0x0021。工具

而咱們的代碼中爲源碼分析

public class TestClass {
  .......
}
複製代碼

你能夠試下將它聲明爲 private 或者其餘狀況是什麼值

類索引、父類索引與接口索引

是什麼

這三個很是相似,因此放在一塊兒講。類索引和父類索引都是u2的數據。接口索引是一組u2類型的數據。由於Java只能單繼承,可是能夠實現多個接口

  • 類索引和父類索引都是指向一個類型爲CONSTANT_Class_info的類描述符常量,經過這個的索引值咱們能夠找到定義在CONSTANT_Utf8_info中的全限定名字符串
  • 接口索引前面有一個接口計數器(interfaces_count),表示後面索引的容量。若是爲零,那麼就表示後面的索引表不佔任何字符。

怎麼看

咱們回到字節碼文件,接着上次看到的 0x0021.

後面是 0x0003, 0x0004,0x0000。這些啥意思呢?

剛剛說了指向CONTANT_Class_info,而這個class_info 存了全限定名的信息,結構以下

在上一篇我也說了,由於他已經肯定了類型,因此是沒有tag的位置的,就只有後面的index。因此0x0003表示它指向 第三個常量。第三個常量是什麼呢?

咱們回到命令行的內容(這個在上篇介紹過怎麼打開的),

第三個常量指向第17個常量,第17個常量是一個 uft8類型的。他的信息爲·com/swu/leosanqing/TestClass。這個不就是該類的名稱嗎?

同理,咱們找到 0x0004指向的含義,他是指向第18個常量,第18個常量也是一個utf8類型的數據,所存儲的信息爲 java/lang/Object,這個類不就是咱們TestClass的父類嗎/

0x0000 表示這個類沒有實現接口(或者這個接口沒有繼承其餘接口),因此這三個就已經解析完了。

字段表

是什麼

字段表(field_info)用於描述接口或類中聲明的變量,字段包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量

而這個field_info是一個這樣的結構

他跟以前的類的訪問標誌很像,都是u2類型的數據,可是有一些差異

若是是相同的話,那麼他們的標誌值是如出一轍的

緊隨access_flags後面的兩個是 name_indexdescriptor_index,這兩個分別表示字段的簡單名稱以及字段和方法的描述符

簡單名稱

簡單名稱是指沒有類型和參數修飾方法或者字段的名字,好比咱們定義的方法

public class TestClass {

    private int m;

    public int inc(){
        return m+1;
    }
}
複製代碼

那麼簡單名稱就是 "inc"和"m".等會兒你跟着我翻譯一下字節碼就知道了

字段和方法的描述符稍微複雜一些,他的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型和順序,看到這三個有沒有想到啥?沒錯,重載!等下就知道重載爲啥要三個不同)和返回值。

根據描述符規則,基本數據類型(byte,short,int…….)以及表明無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L+對象的全限定名來表示

對於數組來講,每個字符將使用一個前置的"["字符來描述,

下面我來分別舉個例子

/* 若是我要表示一個整形數組 int[] 那麼就應該表示爲 [I 若是我要表示一個二維的數組char[][] 那麼就應該表示爲 [[C 若是我要表示一個二維的字符串數組String[][] 那麼就應該表示爲 [[Ljava/lang/String; 注意有分號 由於String不是基本類型也不是void類型,因此要用對象類型來描述 因此他就應該用字符L+String類的全限定名來表示 若是我要表示一個自定義的Person類,他在包 com.swu.leosanqing下 那麼就應該表示爲 Lcom/swu/leosanqing/Person */
複製代碼

描述符(重要)

若是描述符(descriptor_index)要描述一個方法的話,按照先參數列表後返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號"()"中。

下面我舉幾個例子

/* 先拿本程序舉例,他有一個 方法 public int inc(){ return m+1; } 那麼就應該表示爲 "()I" 假設 Person類 在包 "com.swu.leosanqing"下 若是一個方法爲 public Person (char[] source,int[] a,char[][] c,int b,int c,String d). 那麼他就應該表示爲 ([C[I[[CIILjava/lang/String;)Lcom/swu/leosanqing; */
複製代碼

這個後面的方法表集合中會用到

怎麼看

咱們接着類索引、父類索引和接口索引往下看。

咱們對着下面的表進行一一對應,他前面還有一個 field_count

0x0001對應field_count表示只有一個參數,由於代碼中咱們就只定義了一個 private int m

0x0002對應access_flags,而後咱們查上面的表,它的意思爲 字段是private

而後咱們再看 0x0005,它對應的是u2類型的 name_index,咱們應該找常量池中的第5個參數,咱們經過命令行中的找,他的名字是 "m"

而後咱們再往下走,0x0006 ,它對應descriptor_index,而後咱們去常量池中尋找第6個常量,他是"I",而後後面兩個字節是0x0000,表示沒有屬性值。若是個人程序是 private int m = 123;那麼後面的屬性值就應該有表示的值

因此看下來,就是 private int m,跟咱們程序中定義的是如出一轍的,代表咱們的解讀正確。

字段沒法重載

Java中沒有用過字段的重載吧?也就是說咱們不能在程序中這樣定義。

private int m;
public String m;

// 咱們都知道這樣作ide會報錯,兩個字段的數據類型、修飾符不論是否相同
// 都必須使用不同的名字

//可是在字節碼中就不同,若是兩個字段的描述符不一致,那麼他是能夠重名的,且是合法的
複製代碼

下一個小節將方法的重載,這個應該就稍微清晰一點

方法表

是什麼

若是你理解了上一小節的內容,那麼這一小節將會比較好懂,由於兩個都差很少。

他就是用來表示字節碼中的方法的各類數據項

你看這個表是否是很眼熟,和上一小節的字段表結構很是像?

訪問標誌也很像不過有些增減,好比加了方法的修飾符 native 的標誌,刪去了字段的 volatile 標誌

方法裏的代碼去哪了

有沒有發現,方法表結構並無表示或者存放代碼的內容,那麼他去哪裏了呢?他存放到方法表中屬性表裏的 "Code"屬性裏。我會在下一節中講到。

怎麼看

第一個方法

咱們先看下咱們的方法對應字節碼中的是哪些東西。這個恰好是接着上一節的字段表的位置開始的

跟上一節的字段表同樣,首先他有一個表示方法多少的計數器"methods_count",而後纔是方法表(method_info)裏面的數據,因此 0x0002表示裏面有兩個方法。

你可能疑惑了,我只定義了一個inc()方法啊,怎麼會有兩個呢?別忘了,咱們還有一個默認的構造函數。因此有兩個

而後咱們再對應方法表來看

咱們再看下一個 0x0001,它對應access_flags,咱們查方法訪問標誌表,發現他是一個public

而後咱們再看下一個0x0007,他對應name_index,咱們去常量池找第七個常量,發現他是 <init>,這個表示實例構造器.

而後看下一個 0x0008,它對應描述符的索引(descriptor_index),去常量池找,發現他是()V,、

再看下一個 0x0001,它對應屬性值的的個數(attributes_count),說明有1個。

再看下一個 0x0009,他對應屬性值(attributes),咱們查看常量池發現他是表示"Code",Code也是一個複合表,他的各項定義在下一節中會講到。

因此綜合起來這些字節碼就表示 有兩個方法,其中一個是構造函數public void

那咱們定義的public int inc()方法哪去了?這兩個離的比較遠,由於裏面還有其餘的東西。這些東西是啥,下節講屬性表會介紹

第二個方法

根據剛剛分析第一個方法的方法,咱們能夠得出,解析出來是public int inc(),徹底是咱們定義的方法。

方法的重載

Java中,方法的重載只要知足是三個條件的其中一個就行:參數個數不一樣,參數類型不一樣,參數順序不一樣

那是由於Java代碼中要重載一個方法,除了要有相同的名字以外,還得有不一樣的特徵簽名。這個特徵簽名只包括了方法名稱、參數順序、參數類型。

可是字節碼中,特徵簽名不只包括上面的部分,還要包括返回值和受查異常表,若是僅僅只是Java代碼部分的特徵簽名相同,可是返回值或者受查異常表不一樣,那麼字節碼也是容許那樣的同名方法。

屬性表

是什麼

屬性表(attribute_info)在以前的小節中出現了屢次,它是用來描述某些場景的專有信息

好比咱們以前說的Code,還有在字段表小節說的假如 咱們有一個代碼是private int m =123;,屬性表就存123的值。

虛擬機規範預約義了很是很是多屬性

對於每個屬性,他的名稱須要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而這個屬性的結構徹底是自定義的,只須要經過一個u4的長度屬性去說明屬性值所佔用的位數便可。

咱們來看Code屬性表結構(不是每個方法表都有Code屬性,好比接口和抽象類中的方法就沒有這個)

因爲這是Code表結構,並且以前的方法表已經記錄了Code前兩個數據項的內容,因此屬性值的長度固定爲整個屬性表長度減去6個字節,即減去 (u2+u4)

怎麼看

咱們先看他的範圍

前面的4個字節(0x0000001D)對應attribute_length,表示這個Code屬性表一共有多長,總共佔用多少個字節。咱們換算成十進制,就是29,咱們數一下他後面的,恰好是29個字節。從 03後面就是第二個方法的存儲位置了,恰好對應上一節的第二個方法的起始位置,能夠翻回去驗證一下(不過以前的紅線少畫了兩個字節,分析的時候加上了)

後面的 0x0001,0x0001,0x00000005分別對應 max_stack,max_local,code_length.

後面的 2A B7 00 01 B1分別就是字節碼指令了。咱們來翻譯一下

  1. 讀入 2A,查表得0x2A對應的指令爲 aload_0,這個指令的含義是將第0個Slot中爲reference類型的本地變量推送到操做數棧頂

  1. 讀入 B7,查表得 0xB7對應的指令爲 invokespecial,這條指令的做用是以棧頂的reference 類型的數據所指向的對象做爲方法接受者,調用此對象的實例構造器方法、private方法或者他的父類的方法。這個方法有一個u2類型的參數說明具體調用哪個方法,他指向常量池中的一個CONSTANT_Methodref_info 類型常量,即次方法的方法符號調用(就是下一個參數)

  1. 讀入 00 01,這是 上一步 invokespecial的參數,查常量池得 0x0001對應的常量爲 實例構造器"<init>"方法的符號引用

  1. 讀入B1,查表的 0xB1對應的指令爲return,含義是返回此方法,而且返回值爲Void。這條命令執行後,當前方法結束

咱們經過上述的字節碼指令能夠看出,虛擬機的這個執行過程是基於棧的,因此咱們能夠初步推測,Java虛擬機執行字節碼是基於棧的體系結構

後面的那些字節碼是什麼呢?

00 00 00 01 00 0A 咱們接着看code表後面的內容

他的意思分別是 exception_table_length參數爲0,attribute_count參數爲1,0A表示指向常量池的第10個常量,也就是LineNumberTable

咱們來看LineNumberTable的結構

他跟屬性表結構CONTANT_Utf8_info差不太多,因此咱們能夠省掉第一個,由於已經知道他是這個名字了,而後再接着上面的字節碼,解析一下他們分別表示啥。

首先他有一個u4類型的數據表示長度,因此 0x00000006就表示後面的6個字節用來描述這個屬性。後面恰好只有6個字節。

而後一個 u2類型的 line_number_table_length這個就表示有一個行號表,行號表只有兩個u2類型的數據start_pcline_number,前者表示字節碼行號,後者表示Java源碼行號,因此分別是 0和3

經過命令行的結果來驗證咱們是否解析的正確

咱們看到和咱們的解析結果一致。因此咱們的解析過程是正確的

後面的第二個方法你能夠按照個人思路本身解析一下,應該沒有太大的問題。

至此字節碼基本已經解析完

總結

經過兩篇文章咱們知道了字節碼文件中有哪些東西,又該怎麼去看字節碼文件。

Class文件中一共就只有兩種數據類型

  • 無符號數

Class文件中一共有哪些東西呢?大的分類一共有下面幾種

  • 魔數
  • 版本號
  • 常量池
  • 訪問標誌位
  • 類索引、父類索引、接口索引
  • 字段表
  • 方法表
  • 屬性表

固然我這個程序只是很是簡單的程序,還有不少內容用不到,好比異常、接口、同步鎖等,可是已經能夠將整個Class串起來了,若是你想驗證,能夠根據個人方法,自行驗證

相關文章
相關標籤/搜索