羞!扒開字節碼,我竟發現這個.....

做爲一名安卓開發者,咱們能夠以多年單身的手速光速的擼一個java文件。但相信不少人對java的瞭解就像瞭解女神,只看到光鮮的外表。但每每有時候咱們應該看看她卸了妝的樣子,脫了....,咳咳。總之咱們應該深刻的瞭解,這樣能夠幫助咱們作不少有意思的事情。java

最近接觸了asm這個框架,這個框架有多騷?他可以很方便的修改class字節碼文件,在字節碼中插入咱們本身的代碼。實現例如咱們安卓的無痕埋點、字符串加密、方法時間統計等騷操做。數組

因此本文主要經過卸了java字節碼的妝,看看虛擬機眼裏最真實的class文件。本文內容較長,但相信你們耐心慢慢讀,其實並不難,閱讀起來也會比較流暢,固然收穫也是滿滿。bash

卸了java文件的妝

java字節碼文件以.class結尾,是java編譯器編譯咱們平時寫的.java文件生成的。咱們能夠經過命令:網絡

//編譯java文件爲class文件
javac xxx.java
複製代碼

經過命令行編譯後,咱們會獲得一個8位字節的二進制文件,二進制流文件中各個部分以必定的規則緊密的先後排列,相鄰項之間沒有間隙。這樣的好處是可使class文件更加緊湊和小,方便在jvm虛擬機中加載和網絡傳輸。框架

咱們編寫一個名爲Math.java的java源文件,讓咱們先初識一下class文件。jvm

//Math.java
package com.getui.test;

public class Math {
    private int a = 1;
    private int b = 2;
    public int add(){
        return a+b;
    }
}
複製代碼

執行命令:函數

javac Math.java
複製代碼

編譯後咱們會獲得一份Math.class文件,咱們使用010Editor(一款十六進制文件查看和編輯神器)打開Math.class文件。ui

咱們能夠從上圖看到class字節碼的內容,這就是java卸了妝後的樣子。是否是很美?this

010Editor也將咱們把class字節碼文件按照必定的格式,依次解析成了不一樣的數據項。編碼

總體審視class文件的結構

一個class文件包含如下數據項:

描述 類型 解釋
magic u4 魔數,固定:0x CAFE BABE
minor_version u2 java次版本號
major_version u2 java主版本號
constant_pool_count u2 常量池大小
constant_pool[constant_pool_count-1] cp_info(常量表) 字符串池
access_flags u2 訪問標誌
this_class u2 類索引
super_class u2 父類索引
interfaces_count u2 接口計數器
interfaces u2 接口索引集合
fields_count u2 字段個數
fields field_info(字段表) 字段集合
methods_count u2 方法計數器
methods method_info(方法表) 方法集合
attributes_count u2 屬性計數器
attributes attribute_info(屬性表) 屬性集合

上面這個表格是一個字節碼結構表,其中u一、u二、u四、u8是無符號數,它們分別表明1個字節、2個字節、4個字節、8個字節;其中cp_info、field_info、method_info、attribute_info分別表明了常量表、字段表、方法表和屬性表。每個表又具備本身獨特的結構,這會在以後一一介紹。

有了總體的結構,咱們就按這個class字節碼的結構從頭到腳開始一一介紹。

局部審視class文件結構

魔數(magic)

魔數的類型爲u4,因此佔據class文件的4個字節。魔數是用來標識文件類型的一個標誌,而class字節碼文件的魔數固定是0xCAFE BABE。至於爲何是0xCAFE BABE開頭?看下面這個圖你就懂了。

不要想太多,仍是喝杯JAVA吧

版本號(minor_version+major_version)

minor_version的類型爲u2,佔據class文件的2個字節,因此0x00 00表明了編譯.java文件的java次版本爲0。

major_version的類型也爲u2,佔據class文件的2個字節,因此0x00 34,16進制的0x34轉換爲10進製爲52,而JDK1.2版本對應着十進制是46,因此52表明的JDK版本就是1.8版本啦。

結合上面分析,我掐指再一算,當前的JDK版本爲1.8.0版本。

咱們能夠經過命令行進行驗證:

java -version
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b16, mixed mode)
複製代碼

常量池大小(constant_pool_count)

常量池大小的類型也是u2,佔據class文件的2個字節,0x00 16,16進制的0x16轉換爲10進製爲22,則表明了咱們常量池的大小爲22-1=21個。常量池就像咱們class字節碼的倉庫,存放了對這個類的信息描述,例如類名、字段名、方法名、常量值、字符串等。具體的內容咱們會在下一個部分常量池中闡述。

咱們能夠經過命令行,更加簡單的查看常量池中的內容:

javap -verbose ./Math.class
複製代碼

輸入命令後會輸出不少關於Math.class字節碼的內容,但咱們目前就聚焦到Constant pool這一塊:

Constant pool:
   #1 = Methodref #5.#17 // java/lang/Object."<init>":()V
   #2 = Fieldref #4.#18 // com/getui/test/Math.a:I
   #3 = Fieldref #4.#19 // com/getui/test/Math.b:I
   #4 = Class #20 // com/getui/test/Math
   #5 = Class #21 // java/lang/Object
   #6 = Utf8 a
   #7 = Utf8 I
   #8 = Utf8 b
   #9 = Utf8 <init>
  #10 = Utf8 ()V
  #11 = Utf8 Code
  #12 = Utf8 LineNumberTable
  #13 = Utf8 add
  #14 = Utf8 ()I
  #15 = Utf8 SourceFile
  #16 = Utf8 Math.java
  #17 = NameAndType #9:#10 // "<init>":()V
  #18 = NameAndType #6:#7 // a:I
  #19 = NameAndType #8:#7 // b:I
  #20 = Utf8 com/getui/test/Math
  #21 = Utf8 java/lang/Object
複製代碼

這裏咱們能夠看到,這裏的Constant pool好像有點眼熟,都是咱們java中寫的部分代碼,但又以爲怪怪的。這裏先無論,以後在介紹cp_info的時候會具體介紹。咱們先留一個大概印象就行了。

味道怪怪的

這裏還有一個問題,尚未解決,常量池的大小爲何須要減1,例如咱們0x16的十六進制轉換爲十進制是22,爲何說常量池大小爲22-1=21個?咱們寫代碼時,數組下標都是從0開始,而咱們看到上面命令行展現的內容,Constant pool是從1開始,它將第0項的常量空出來了。而這個第0項常量它具有着特殊的使命,就是當其餘數據項引用第0項常量的時候,就表明着這個數據項不須要任何常量引用的意思。

cp_info(常量表)

cp_info主要存放字面量和符號引用。

它主要包含如下14種類型:

類型 標誌 描述
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_MothodType_info 16 標誌方法類型
CONSTANT_InvokeDynamic_info 18 表示一個動態方法調用點

其中每一個類型的結構又不盡相同,你們能夠查看下面這個表格:

接下來讓咱們開始解析字節碼的常量部分:

CONSTANT_Methodref_info{
	u1 tag;
	u2 class_index;
	u2 name_and_type_index;
}
複製代碼

咱們開始分析第1個常量,咱們看到常量的tag的值等於0x0A,轉換爲十進制爲10,對照上面的第一個表,咱們能夠獲得這個常量類型爲CONSTANT_Methodref_info,接着往下看,對照第二個表CONSTANT_Methodref_info還有2個部分的索引值,第一個是Constant_Class_Info的值,它佔2個字節,因此它的值是0x00 05,轉化爲十進制爲5。接下來咱們看看第五個常量的16進制。

CONSTANT_Class_info{
	u1 tag;
	u2 name_index;
}
複製代碼

第5個常量的tag爲0x07,轉換爲十進制爲7,對照第一個表,咱們能夠獲得這個常量類型爲CONSTANT_Class_info,接下來按照基本套路往下看,咱們對照第二個表CONSTANT_Class_info剩下部分的2個直接指向全限定名常量項的索引0x00 14,轉換爲十進制爲21。接下來咱們繼續看一下第21個常量賣的是什麼瓜。

CONSTANT_utf8_info{
	u1 tag;
	u2 length;
	length bytes[];
}
複製代碼

咱們能夠看到第21個常量的tag爲0x01,轉換爲十進制爲1,對照第一個表,咱們能夠獲得這個常量類型爲CONSTANT_utf8_info,接下來按照國際慣例和基本套路,咱們繼續對照第二個表,咱們能夠知道CONSTANT_utf8_info的第二個部分佔2個字節,即0x00 10,轉換爲十進制爲16,則表明着接下來有長度爲16的UTF-8編碼字符串;接下來第三部分爲長度爲16個字節,即0x6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74,表明着字符串爲java/lang/Object。這個java/lang/Object就是一個全限定名,全限定名就是基本類型除外的類,將它的包名中的.換成/

很好,咱們的革命成功了一半,還記得咱們分析第一個常量的時候,咱們只分析到第二部分嗎?第三部分佔兩個字節,指向名稱及類型描述符CONSTANT_NameAndType的索引,它的值是0x00 11(忘記了的朋友能夠往上翻看第1個常量解析的圖片),轉換爲十進制爲17,因此咱們查看第17個常量。

咱們查看前一個字節tag爲0C,轉換爲十進制爲12,表明了CONSTANT_NameAndType_info類型,廢話很少說,查看第二個表格,第二個部分佔2個字節,指向該方法或字段名稱常量項的索引,其值爲0x00 09,轉換爲十進制爲9,咱們直接查看第9個常量。

依舊是一個CONSTANT_utf8_info類型,其結構我就再也不多述,小夥伴本身嘗試着分析試試。

通過解析咱們能夠知道它是一個長度爲6的UTF-8編碼字符串,其值爲<init>

接着分析CONSTANT_NameAndType的第三部分,佔用兩個字節,指向其字段或方法描述符的常量索引,其值爲0x00 0A,轉換爲十進制爲10;查看第10個常量。

仍是一個CONSTANT_utf8_info類型,其結構的意義是長度爲3的UTF編碼字符串,其值爲()V

哎,這個值好像看着有點怪怪的。()V是啥東東。

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

標誌符 含義
B 基本數據類型byte
C 基本數據類型char
D 基本數據類型double
F 基本數據類型float
I 基本數據類型int
J 基本數據類型long
S 基本數據類型short
Z 基本數據類型boolean
V 基本數據類型void
L 對象類型,如Ljava/lang/Object

對於數組來講,咱們對每一維度經過在類型前加[來表示,例如一個int[]數組,咱們會經過[I表示,好比一個二位數組java.lang.Object[][],則用[[Ljava/lang/Object;表示。

對於()V中的()則表示方法的參數列表,其中的V表明返回值Void,咱們知道咱們java中定義一個類,沒有定義構造函數的時候,Java會自動幫咱們生成一個無參構造函數。因爲是無參構造函數,且返回值是Void,因此表示爲()V

例如咱們public void add(int a, int b),則表示爲(II)V。在例如public String getContent(int type)則表示爲(I)Ljava/lang/Object

好的,介紹了這麼久,咱們其實直接介紹了常量池中的一個常量。

Constant pool:
	//咱們只介紹了下面#1這個常量
   #1 = Methodref #5.#17 // java/lang/Object."<init>":()V
   #2 = Fieldref #4.#18 // com/getui/test/Math.a:I
   #3 = Fieldref #4.#19 // com/getui/test/Math.b:I
   #4 = Class #20 // com/getui/test/Math
   #5 = Class #21 // java/lang/Object
   #6 = Utf8 a
   #7 = Utf8 I
   #8 = Utf8 b
   #9 = Utf8 <init>
  #10 = Utf8 ()V
  #11 = Utf8 Code
  #12 = Utf8 LineNumberTable
  #13 = Utf8 add
  #14 = Utf8 ()I
  #15 = Utf8 SourceFile
  #16 = Utf8 Math.java
  #17 = NameAndType #9:#10 // "<init>":()V
  #18 = NameAndType #6:#7 // a:I
  #19 = NameAndType #8:#7 // b:I
  #20 = Utf8 com/getui/test/Math
  #21 = Utf8 java/lang/Object
複製代碼

爲了把一個常量說明白,不知不覺說了這麼多。剩餘的常量我相信小夥伴們應該具有觸類旁通的能力了。其實更多的時間咱們不須要這樣一個一個解析字節碼,之因此這樣帶着你們解析,只是爲了讓你們感覺字節碼的魅力和常量的結構。更多時候,咱們經過以前說的命令,一行搞定。

javap -verbose ./Math.class
複製代碼

訪問標示

訪問標示佔據2個字節,訪問標示表示類或者接口的訪問信息。標示信息對應以下:

標誌名稱 十六進制標誌值 二進制標記值 含義
ACC_PUBLIC 0x0001 1 是否爲Public類型
ACC_FINAL 0x0010 10000 是否被聲明爲final,只有類能夠設置
ACC_SUPER 0x0020 100000 是否容許使用invokespecial字節碼指令的新語義,JDK1.0.2以後編譯出來的類的這個標誌默認爲真
ACC_INTERFACE 0x0200 1000000000 標誌這是一個接口
ACC_ABSTRACT 0x0400 10000000000 是否爲abstract類型,對於接口或者抽象類來講,此標誌值爲真,其餘類型爲假
ACC_SYNTHETIC 0x1000 1000000000000 標誌這個類並不是由用戶代碼產生
ACC_ANNOTATION 0x2000 10000000000000 標誌這是一個註解
ACC_ENUM 0x4000 100000000000000 標誌這是一個枚舉

咱們知道咱們的類是一個public修飾的類,其十六進制值爲0x00 21,因此咱們能夠參照十六進制的表格,得出其爲ACC_SUPER+ACC_PUBLIC

可是若是咱們只想判斷這個類是否是存在某個標示,咱們應該如何判斷呢,好比咱們只想判斷是否這個類是否被ACC_PUBLIC修飾,經過二進制的列咱們能夠看出,每一個標識符在某一位的值爲1,咱們能夠經過這個標識符的二進制與要判斷的這個標識符取與操做便可判斷這個標識符是否被某標示符修飾。例如:

0x21的二進制爲:100001,ACC_PUBLIC的二進制爲:1,100001&1的結果爲1。因此咱們能夠判斷這個類包含ACC_PUBLIC訪問標識符。

類索引

類索引佔用2個字節,指向該類的CONSTANT_Class常量,其值爲0x00 04,轉換爲十進制爲4,及第四個常量。

#4 = Class #20 // com/getui/test/Math
複製代碼

咱們能夠看到類索引指向了該類的全限定名。

父類索引

父類索引佔有2個字節,指向該類的父類,其值爲0x00 05,轉換爲十進制爲5,及第五個常量。

#5 = Class #21 // java/lang/Object
複製代碼

咱們的Math類沒有繼承任何類,因此其默認的父類是Object類。

接口計數器

接口計數器表示該類實現了幾個接口,即implements了幾個接口。因爲咱們的Math沒有實現接口,其值爲0x00 00,轉換爲十進制也爲0。

接口索引集合

接口索引集合是一個集合,包含了全部實現的接口的索引,每一個接口索引佔用2個字節,指向常量中的接口。

因爲Math.java沒有實現任何接口,因此不存在這部分的值。須要驗證的小夥伴能夠本身自定義一個類,實現幾個接口進行驗證。也是很是簡單的。

字段個數

字段個數佔2個字節,表示以後有多少個字段,字段主要用來描述類或者接口中聲明的變量。這裏的字段包含了類級別變量以及實例變量,可是不包括方法內部聲明的局部變量。

咱們看到字段個數的值爲0x00 02,轉換爲十進制爲2,即以後有2個字段。

field_info(字段表)

field_info{
	u2 access_flags;//訪問標誌	
	u2 name_index;//字段名索引	
	u2 descriptor_index;//描述符索引	
	u2 attributes_count;//屬性計數器	
	attribute_info attributes;//屬性集合	
}
複製代碼

因爲咱們Math.java有兩個字段,即private int a = 1;private int b = 2;,咱們這裏只分析int a的字段。

標誌名稱 十六進制標誌值 二進制標記值 含義
ACC_PUBLIC 0x0001 1 字段是否爲public
ACC_PRIVATE 0x0002 10 字段是否爲private
ACC_PROTECTED 0x0004 100 字段是否爲protected
ACC_STATIC 0x0008 1000 字段是否爲static
ACC_FINAL 0x0010 10000 字段是否爲final
ACC_VOLATILE 0x0040 1000000 字段是否爲volatile
ACC_TRANSTENT 0x0080 10000000 字段是否爲transient
ACC_SYNCHETIC 0x1000 1000000000000 字段是否爲由編譯器自動產生
ACC_ENUM 0x4000 100000000000000 字段是否爲enum

第一部分access_flags佔2個字節,其值爲0x00 02,轉換爲十進制爲2,轉化爲二進制爲10,咱們能夠對照上表,能夠知道該字段的訪問標誌爲ACC_PRIVATEprivate

第二部分name_index佔2個字節,其值爲0x 00 06,轉換爲十進制爲6,咱們直接在常量池中找到第6個常量

#6 = Utf8 a
複製代碼

咱們能夠看到name_index的索引指向的就是a這個變量名。

第三部分descriptor_index佔2個字節,其值爲0x 00 07,轉換爲十進制爲7,咱們直接在常量池中找到第7個常量

#7 = Utf8 I
複製代碼

咱們能夠看到descriptor_index的索引指向的就是a變量的類型,I表明了int類型。

第四部分attributes_count佔兩個字節,其值爲0x00 00,轉換爲十進制爲0,則表明private a =1; 沒有屬性集合。

若是第四部分的值不爲0,則會存在attributes集合屬性,有興趣的小夥伴能夠自行研究。

方法計數器

方法計數器佔2個字節,表示後面有多少個方法。在這裏咱們的方法計數器的值爲0x00 02,轉換爲十進制爲2。

有的小夥伴可能會問,你不是隻定義了一個add方法,爲啥這邊方法數爲2?還記得嗎?java在咱們自定義類的時候,即便咱們不實現任何一個構造函數的時候,java會默認替咱們增長一個無參的構造函數。因此Math這個類具備無參構造函數add這兩個方法,因此方法計數器的值爲2。

method_info(方法表)

method_info{
	u2 access_flags; //方法訪問標誌
	u2 name_index; //方法名稱索引
	u2 descriptor_index; //方法描述符索引
	u2 attributes_count; //屬性計數器	
	struct attribute_info{
		u2 attribute_name_index; //屬性名的索引
		u4 attribute_length; //屬性的長度
		attribute_length info[]
	}
}
複製代碼
標誌名稱 十六進制標誌值 二進制標誌值 含義
ACC_PUBLIC 0x0001 1 方法是否爲public
ACC_PRIVATE 0x0002 10 方法是否爲private
ACC_PROTECTED 0x0004 100 方法是否爲protected
ACC_STATIC 0x0008 1000 方法是否爲static
ACC_FINAL 0x0010 10000 方法是否爲final
ACC_SYHCHRONRIZED 0x0020 100000 方法是否爲synchronized
ACC_BRIDGE 0x0040 1000000 方法是不是有編譯器產生的方法
ACC_VARARGS 0x0080 10000000 方法是否接受參數
ACC_NATIVE 0x0100 100000000 方法是否爲native
ACC_ABSTRACT 0x0400 10000000000 方法是否爲abstract
ACC_STRICTFP 0x0800 100000000000 方法是否爲strictfp
ACC_SYNTHETIC 0x1000 1000000000000 方法是不是有編譯器自動產生的

咱們來分析一下無參構造函數,首先咱們先看第一部分,access_flags佔2個字節,其值爲0x00 01,轉換爲十進制爲1,轉換爲二進制爲1,參照上表,咱們能夠知道無參構造函數是被ACC_PUBLIC修飾,即public修飾。

第三部分descriptor_index佔2個字節,其值爲0x00 0A,轉換爲十進制爲10,咱們繼續看常量池中第10個常量

#10 = Utf8 ()V
複製代碼

descriptor_index表明了這個方法的描述,在前面已經解析過()V的含義,它表明了該方法沒有參數列表,而且返回值爲Void。

第四部分attributes_count佔2個字節,表明屬性計數器,記錄着該方法有幾個屬性。其值爲0x00 01,轉換爲十進制爲1,表明該方法具備一個屬性,接着往下看。

該方法只有一個屬性,因此只有一個attribute_info類型的屬性。屬性的結構第一部分attribute_name_index佔用兩個字節,其值爲0x00 0B,轉換爲十進制爲11。繼續查找查找常量池

#11 = Utf8 Code
複製代碼

這個方法名爲code,表明着這個屬性符合code屬性表。

struct attribute_info{
		u2 attribute_name_index; //屬性名的索引
		u4 attribute_length; //屬性的長度
		u2 max_stack;//操做數棧深度的最大值
		u2 max_locals;//局部變量表所需的存續空間
		u4 code_length;//字節碼指令的長度
		u1 code; //code_length個code,存儲字節碼指令
		u2 exception_table_length;//異常表長度
		exception_info exception_table;//exception_length個exception_info,組成異常表
		u2 attributes_count;//屬性集合計數器
		attribute_info attributes;//attributes_count個attribute_info,組成屬性表
	}
複製代碼

咱們把code屬性的十六進制字節碼提取出來方便查看:

00 02 00 01 00 00 00 0A 2A B4 00 02 2A B4 00 03 60 AC 00 00 00 01 00 0C 00 00 00 06 00 01 00 00 00 07
複製代碼

咱們按照上面這個表格開讀:

不要走我偷小魚乾養你

attribute_name_index的值0x00 0B咱們已經分析過。

attribute_length佔4個字符,其值爲0x00 00 00 22,轉換爲十進制爲34,表明後面的34個字節都是code屬性部分。

max_stack佔2個字節,其值爲0x00 02,表明操做數棧最大深度爲2。關於操做數棧相關的知識,讀者能夠手動谷歌。

max_locals佔2個字節,其值0x00 01,表明局部變量表所需的連續空間爲1。

code_length佔4個字節,其值爲0x00 00 00 0A,轉換爲十進制爲10,表明後面的10個字節屬於字節碼指令集部分。

code佔10個字節,0x2A B4 00 02 2A B4 00 03 60 AC,這可不是轉換爲十進制去看了,咱們能夠參考這篇博客對照其表格,把對應的十六進制轉換爲指令集。轉換爲指令集以下

2A->aload_0 
B4->getfield 
00->nop 
02->對應常量池第二項 Field a:I
2A->aload_0
B4->getfield
00->nop
03->對應常量池第三項 Field b:I
60->iadd
AC->ireturn
複製代碼

對應的意思指令集意思能夠對照上面的博客進行參考,有機會我也會整理一篇相關的博客。

有同窗可能會說,你上面扯了一大堆,你怎麼證實你解讀就是對的,我不信,我不聽,我不看。

好好好,其實咱們經過命令行就能夠獲得驗證:

javap -verbose ./Math.class
  public int add();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2 // Field a:I
         4: aload_0
         5: getfield      #3 // Field b:I
         8: iadd
         9: ireturn
      LineNumberTable:
        line 7: 0
複製代碼

接下來咱們繼續分析(扯淡)

exception_table_length佔2個字節,其值爲0x00 00,轉換爲十進制爲0;這裏存放的是處理異常的信息。 每一個exception_table表項由start_pc,end_pc,handler_pc,catch_type組成。start_pc和end_pc表示在code數組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異常會由這個表項來處理;handler_pc表示處理異常的代碼的開始處。catch_type表示會被處理的異常類型,它指向常量池裏的一個異常類。當catch_type爲0時,表示處理全部的異常,這個能夠用來實現finally的功能。

因爲咱們這裏的值是0,也就不展開介紹了,你們能夠自行研究。

attributes_count佔2個字節,其值爲0x00 01,轉化爲十進制爲1;表示有一個附加屬性。

attribute_name_index佔2個字節,其值爲0x00 0C,轉換爲十進制爲12;其含義是附加屬性在常量池的位置,指向常量池的第12項,它的類型是LineNumberTable。其結構爲:

LineNumberTable_attribute {
          u2 attribute_name_index;
          u4 attribute_length;
          u2 line_number_table_length;
          struct line_number_table{ 
          	u2 start_pc;
          	u2 line_number;
     			} 
}
複製代碼

attribute_length佔4個字節,其值爲0x00 00 00 06,表示後面長度6個字節爲attribute。

line_number_table_length佔2個字節,其值爲0x00 01,轉換爲十進制爲1,表明LineNumberTable有一項值。

start_pc佔2個字節,其值0x00 00,轉換爲十進制爲0,表明字節碼行號。

line_number佔2個字節,其值0x00 07,轉換爲十進制爲7,表明java源碼的行號爲第7行。

附加屬性

屬性個數(attribute_count)

attribute_length佔2個字節,其值爲0x00 01,轉換爲十進制爲1,表明後面有一項附加屬性值。

屬性結構(attribute_info_attributes)

SourceFile_attribute {
     u2 attribute_name_index;
     u4 attribute_length;
     u2 sourcefile_index;
}
複製代碼

attribute_name_index佔2個字節,其值爲0x 00 0F,轉換爲十進制爲15,表明在常量池中第15項,查看15項能夠獲得是SourceFile,說明這個屬性是Source。

attribute_length佔4個字節,其值爲0x00 00 00 02,轉換爲十進制爲2,表明後面有2個字節爲attribute的內容部分。

sourcefile_index佔2個字節,其值爲0x00 10,轉換爲十進制爲16,表明在常量池中第16項,查看16項能夠獲得Math.java的值,表明着個class字節碼文件的源碼名爲Math.java

總結

終於終於把這篇有關字節碼的文章寫完了,其實咱們常常狀況下並不須要直接讀枯燥無味的16進制字節碼,咱們經過 javap命令行就可以自動轉換字節碼的結構。

寫這篇字節碼,只是爲了讓你們對字節碼有更深入的印象和理解,也幫助咱們之後可以更自信和熟練的使用相似ASM等字節碼插樁框架。

熟練使用ASM字節碼插樁框架,咱們可以配合Gradle插件和註解開發出不少騷操做,例如無痕埋點統計、Java層字符串加密,再熟悉一點本身手擼一個相似Butterknife的框架等。

最後爲你們奉獻上010Editor的Mac破解版

連接: pan.baidu.com/s/1vTxPTSfJ… 提取碼: pa8d

相關文章
相關標籤/搜索