最近有時會聊到java類的字節碼,作了這麼多年android開發,還真不瞭解它,因而想探索一下。java
先寫了一個簡單的java類MyClass,代碼以下:android
public class MyClass {
private int i;
public void inc() {
i++;
}
}
複製代碼
而後分析它的class文件。找到生成的MyClass.class以後,用vim -b MyClass.class
打開。打開後,是這樣的,一臉懵逼。vim
沒關係,在vim中輸入命令:%! xxd
,展示這樣的內容:數組
把字節碼取出來,逐個分析。bash
每一個Class文件的頭四個字節稱爲魔數,它的惟一做用是用來肯定該文件是否爲一個能被虛擬機接受的Class文件this
對應java class的版本號。spa
u2 minor_version;//次版本號
u2 major_version;//主版本號
複製代碼
次版本號在前,主版本號在後。minor_version是0,major_version是16進制的35。.net
u2 constant_pool_count;//常量池容量計數
複製代碼
換算成10進制,常量的個數是21,這就表明常量池中有20項常量,索引值範圍爲1~20。(計數從1開始,其餘計數從0開始,由於0有其餘做用)3d
選中部分既是常量池。 調試
分析常量池得出如下幾項
若是用javap -p -v MyClass.class
看,則是
接下來分析一下每一項的含義
該常量類型是CONSTANT_Methodref_info
它的結構體是
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
複製代碼
items | 描述 |
---|---|
tag | CONSTANT_Methodref_info結構的tag值爲10 |
class_index | CONSTANT_Methodref_info結構的class_index必須是一個類類型,而不是接口類型 |
name_and_type_index | name_and_type_index的值必須是constant_pool表中的一個有效索引;這索引值上的對應的常量池條目(The constant_pool entry)也必定是CONSTANT_NameAndType_info結構;這個條目也是具備字段或方法做爲成員的類或接口類型 |
它的class_index爲4,對應的是第四項,它的name_and_type_index是17。咱們先分析下第四項是什麼。
這是第4項,07表明CONSTANT_class,它的結構是
CONSTANT_Class_info {
u1 tag;
u2 index;
}
複製代碼
index爲14,表明的是索引值是20的那一項,咱們再順着分析下第20項。
第20項是CONSTANT_Utf8_info,tag是1,它的結構是
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
複製代碼
第20項的值是01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
,其中tag是1,length是16,bytes是java/lang/Object
(表示的字符串是 java/lang/Object)
回過頭來咱們分析下第17項的name_and_type_index
這是第17項,它的結構是
CONSTANT_Name_AndType_info {
u1 tag; // 值是12
u2 index; // 指向該字段或方法名稱常量項的索引
u2 index; // 指向該字段或方法描述符常量項的索引
}
複製代碼
在這裏,兩個index分別是7和8,因而咱們又轉向第7項和第8項,第7項和第8項能夠看出來,他們的值分別是01 00 06 3c 69 6e 69 74 3e
和01 00 03 28 29 56
。它們又都是CONSTANT_Utf8_info結構,字符串分別是<init>
和()V
。
方法名稱<init>
和方法描述符()V
指的是什麼呢?指的是一個實例初始方法,沒有參數,返回類型也必定是 void。
回過頭來我理一下它們的關聯,用圖來表示以下。
因此#1 Methodref表示的含義是java/lang/Object."<init>":()V
一樣道理,分析第2項是。用圖來表示以下。
因此#2 Fieldref表示的含義是MyClass.i:I
,MyClass類的字段i,類型是整型。
至於別的常量,後邊再分析。
MyClass是一個普通Java類,不是接口、枚舉或者註解,被public關鍵字修飾但沒有被聲明爲final和abstract,而且它使用了JDK 1.2以後的編譯器進行編譯,所以它的ACC_PUBLIC、ACC_SUPER標誌應當爲真,而ACC_FINAL、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、CC_ENUM這6個標誌應當爲假,所以它的access_flags的值應爲:0x0001|0x0020=0x0021。
00 21
以後就是00 03 00 04
,00 03
表明上邊常量池中的第3項MyClass,這是類索引,00 04
表明上邊常量池中第4項java/lang/Object,這是父類索引。
緊接着是00 00
表示接口索引的個數。由於MyClass沒有實現接口,因此個數是0。
緊接着是字段個數 00 01
,表示有一個字段。那固然是i了,接下來看字段i在字節碼中是如何表示的。
這8個字節表示的結構是:
field_info {
u2 access_flags; // 2
u2 name_index; // 5
u2 descriptor_index; // 6
u2 attributes_count; // 0
attribute_info attributes[attributes_count]; // null
}
複製代碼
access_flags是2,表示ACC_PRIVATE,私有成員。name_index是5,是常量池中的第5項i,descriptor_index是6,是常量池中的第6項,I,表明整型。
留下來個問題,attributes_count爲何是0,attribute_info在這裏是什麼含義?
接下來的字節碼存儲的是java的方法
00 02
表示兩個方法。爲何是兩個方法,固然是和inc了。且看是如何存儲在字節碼中的。
第一個方法在字節碼中的存儲是
首先映入眼簾的是00 01 00 07 00 08 00 01
,00 01
表示ACC_PUBLIC,說明是public方法,00 07
是常量池第7項索引,表示方法名<init>
,00 08
是常量池第8項索引,表示方法描述符()V
。00 01
表示attributes_count, 對應的結構是
method_info {
u2 access_flags; // 1
u2 name_index; // 7
u2 descriptor_index; // 8
u2 attributes_count; // 1
attribute_info attributes[attributes_count];
}
複製代碼
那麼剩下的就是屬性信息了,對應的結構是
attribute_info {
u2 attribute_name_index; // 9
u4 attribute_length; // 00 00 00 2f,即47
u1 info[attribute_length];
}
複製代碼
00 09
表示常量池中屬性名稱索引是第9項,常量池中第9項是Code,說明屬性名稱是Code。
u1 info[attribute_length]
對應的字節碼信息是:
這些字節碼存儲的信息是:
其中字節碼指令就是2a b7 00 01 b1
,分析這些指令。
經過javap -p -v MyClass.class能夠看到
剩下的字節碼是
000a
0000 0006 0001 0000 0001 000b 0000 000c
0001 0000 0005 000c 000d 0000
複製代碼
剩下的字節碼錶示attributes,這些attributes是什麼呢?attributes一共有2個屬性,LineNumberTable和LocalVariableTable。
其中000a 0000 0006 0001 0000 0001
表明LineNumberTable_attribute這個結構,這個結構是
LineNumberTable_attribute {
u2 attribute_name_index; // 00 0a,常量池中的第10項
u4 attribute_length; // 0000 0006,屬性長度是6個字節長
u2 line_number_table_length; // 0001,只有1個line_number_table
{ u2 start_pc; // 0000,字節碼行號
u2 line_number; // 0001,Java源碼行號
} line_number_table[line_number_table_length];
}
複製代碼
LineNumberTable的做用在於,若是選擇不生成LineNumberTable屬性,對程序運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,而且在調試程序的時候,也沒法按照源碼行來設置斷點。
其中000b 0000 000c 0001 0000 0005 000c 000d 0000
表示LocalVariableTable_attribute,這個結構是
LocalVariableTable_attribute {
u2 attribute_name_index; // 000b常量池中第11項
u4 attribute_length; // 000 000c,屬性長度是12個字節
u2 local_variable_table_length; // 0001,只有1個local_variable_table
{ u2 start_pc; // 0000
u2 length; // 0005
u2 name_index; // 000c,常量池中的第12項,this
u2 descriptor_index; // 000d
u2 index; // 0000
} local_variable_table[local_variable_table_length];
}
複製代碼
LocalVariableTable屬性用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關係。能夠看到裏邊有個local_variable_table。它的含義以下。
start_pc和length 表明了這個局部變量的生命週期開始的字節碼偏移量及其做用範圍覆蓋的長度,二者結合起來就是這個局部變量在字節碼之中的做用域範圍。
name_index和descriptor_index 指向常量池中CONSTANT_Utf8_info型常量的索引,分別表明了局部變量的名稱以及這個局部變量的描述符
index 這個局部變量在棧幀局部變量表中Slot的位置。當這個變量數據類型是64位類型時(double和long),它佔用的Slot爲index和index+1兩個
咱們再來分析第2個方法,它的字節碼一共是
簡單分析入下: 0001 000e 0008 0001
表示的是
method_info {
u2 access_flags; // 0001, ACC_PUBLIC, 表示public方法
u2 name_index; // 000e, 常量池索引,表示第14項,inc
u2 descriptor_index; // 0008,常量池索引,表示第8項,()V
u2 attributes_count; // 0001,1個attribute_info
attribute_info attributes[attributes_count];
}
複製代碼
0009 0000 0039
表示的是
attribute_info {
u2 attribute_name_index; // 0009,常量索引,表示Code
u4 attribute_length; // 0000 0039, 十進制是57,表示info的長度
u1 info[attribute_length]; // 長度爲57,該表示該java方法的字節塊的剩餘部分
}
複製代碼
再來分析info[57],是
字節碼 | 名字 | 含義 |
---|---|---|
0003 | max_stack | 操做數棧(Operand Stacks)深度的最大值 |
0001 | max_locals | max_locals表明了局部變量表所需的存儲空間 |
0000 000b | code_length | 表明字節指令碼的長度 |
2a59 b400 0204 60b5 0002 b1 | code | code指令碼 |
00 00 | exception_table_length | 異常表長度 |
無 | exception_table | |
00 02 | attributes_count | 屬性數量 |
00 0a00 0000 0a00 0200 0000 0500 0a00 06 | LineNumberTable | |
00 0b00 0000 0c00 0100 0000 0b00 0c00 0d00 00 | LocalVariableTable |
其中00 0a00 0000 0a00 0200 0000 0500 0a00 06
對應的是
LineNumberTable_attribute {
u2 attribute_name_index; // 00 0a
u4 attribute_length; // 00 00 00 0a
u2 line_number_table_length; // 00 02
// 這裏line_number_table數組長是2,分別是[ {00 00, 00 05},{00 0a, 00 06}]
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
複製代碼
上邊內容已經分析過它的含義,這裏再也不分析。
其中00 0b00 0000 0c00 0100 0000 0b00 0c00 0d00 00
對應的是
LocalVariableTable_attribute {
u2 attribute_name_index; // 00 0b,常量池索引
u4 attribute_length; // 00 00 00 0c
// 這裏應該也是在描述this這個變量
u2 local_variable_table_length; // 00 01
{ u2 start_pc; // 00 00
u2 length; // 00 0b 字節碼中的做用域範圍
u2 name_index; // 00 0c
u2 descriptor_index; // 00 0d
u2 index; // 00 00
} local_variable_table[local_variable_table_length];
}
複製代碼
注意,注意,其中關鍵的code碼指令2a59 b400 0204 60b5 0002 b1
,咱們沒有分析。
code碼 | 指令 | 描述 |
---|---|---|
2a | aload_0 | 第0個Slot中爲reference類型的本地變量(一般是this)推送到操做數棧頂 |
59 | dup | 複製棧頂。至關於把操做數棧頂元素pop出來,再把它push進去2次 |
b4 00 02 | getfield | 獲取對象的字段,將其值壓入棧頂 |
04 | iconst_1 | int型常量1進棧 |
60 | iadd | 加法 |
b5 00 02 | putfield | 給對象的字段賦值 |
b1 | return | 返回此方法,而且返回值爲void |
00 0100 0f00 0000 0200 10
表示什麼含義呢?
00 01 表示attribute count爲1
00 0f00 0000 0200 10 表示attribute_info
attribute_info {
u2 attribute_name_index; // 00 0f,常量池中的SourceFile
u4 attribute_length; // 00 00 00 02,屬性長度是2
u1 info[attribute_length]; // 00 10,常量池第16項的MyClass.java
}
複製代碼
這是ClassFile最後的1個屬性。
至此,大概分析了一下。 感謝blog.csdn.net/qq_31156277…,整個分析過程是靠這篇博客的講解來的。