做爲一名安卓開發者,咱們能夠以多年單身的手速光速的擼一個java文件。但相信不少人對java的瞭解就像瞭解女神,只看到光鮮的外表。但每每有時候咱們應該看看她卸了妝的樣子,脫了....,咳咳。總之咱們應該深刻的瞭解,這樣能夠幫助咱們作不少有意思的事情。java
最近接觸了asm這個框架,這個框架有多騷?他可以很方便的修改class字節碼文件,在字節碼中插入咱們本身的代碼。實現例如咱們安卓的無痕埋點、字符串加密、方法時間統計等騷操做。數組
因此本文主要經過卸了java字節碼的妝,看看虛擬機眼裏最真實的class文件。本文內容較長,但相信你們耐心慢慢讀,其實並不難,閱讀起來也會比較流暢,固然收穫也是滿滿。bash
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文件包含如下數據項:
描述 | 類型 | 解釋 |
---|---|---|
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字節碼的結構從頭到腳開始一一介紹。
魔數的類型爲u4,因此佔據class文件的4個字節。魔數是用來標識文件類型的一個標誌,而class字節碼文件的魔數固定是0xCAFE BABE。至於爲何是0xCAFE BABE開頭?看下面這個圖你就懂了。
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)
複製代碼
常量池大小的類型也是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主要存放字面量和符號引用。
它主要包含如下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{
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_PRIVATE
即private
。
第二部分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{
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_length佔2個字節,其值爲0x00 01,轉換爲十進制爲1,表明後面有一項附加屬性值。
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
。
javap
命令行就可以自動轉換字節碼的結構。
寫這篇字節碼,只是爲了讓你們對字節碼有更深入的印象和理解,也幫助咱們之後可以更自信和熟練的使用相似ASM等字節碼插樁框架。
熟練使用ASM字節碼插樁框架,咱們可以配合Gradle插件和註解開發出不少騷操做,例如無痕埋點統計、Java層字符串加密,再熟悉一點本身手擼一個相似Butterknife的框架等。
最後爲你們奉獻上010Editor的Mac破解版
連接: pan.baidu.com/s/1vTxPTSfJ… 提取碼: pa8d