對java的class文件的字節碼的分析

最近有時會聊到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

cafe babe

每一個Class文件的頭四個字節稱爲魔數,它的惟一做用是用來肯定該文件是否爲一個能被虛擬機接受的Class文件this

0000 0035

對應java class的版本號。spa

u2 minor_version;//次版本號
 u2 major_version;//主版本號
複製代碼

次版本號在前,主版本號在後。minor_version是0,major_version是16進制的35。.net

0015

u2  constant_pool_count;//常量池容量計數
複製代碼

換算成10進制,常量的個數是21,這就表明常量池中有20項常量,索引值範圍爲1~20。(計數從1開始,其餘計數從0開始,由於0有其餘做用)3d

選中部分既是常量池。 調試

分析常量池得出如下幾項

若是用javap -p -v MyClass.class看,則是

接下來分析一下每一項的含義

0a 00 04 00 11

該常量類型是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。咱們先分析下第四項是什麼。

07 00 14

這是第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

0c 00 07 00 08

這是第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 3e01 00 03 28 29 56。它們又都是CONSTANT_Utf8_info結構,字符串分別是<init>()V

方法名稱<init>和方法描述符()V指的是什麼呢?指的是一個實例初始方法,沒有參數,返回類型也必定是 void。

回過頭來我理一下它們的關聯,用圖來表示以下。

因此#1 Methodref表示的含義是 java/lang/Object."<init>":()V

09 00 03 00 12

一樣道理,分析第2項是。用圖來表示以下。

因此#2 Fieldref表示的含義是 MyClass.i:I,MyClass類的字段i,類型是整型。

至於別的常量,後邊再分析。

常量池後邊的00 21

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 0400 03表明上邊常量池中的第3項MyClass,這是類索引,00 04表明上邊常量池中第4項java/lang/Object,這是父類索引。

緊接着是00 00表示接口索引的個數。由於MyClass沒有實現接口,因此個數是0。

field

field count

緊接着是字段個數 00 01,表示有一個字段。那固然是i了,接下來看字段i在字節碼中是如何表示的。

field 結構

這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在這裏是什麼含義?

methods

接下來的字節碼存儲的是java的方法

methods count

00 02表示兩個方法。爲何是兩個方法,固然是和inc了。且看是如何存儲在字節碼中的。

第一個方法

第一個方法在字節碼中的存儲是

首先映入眼簾的是00 01 00 07 00 08 00 0100 01表示ACC_PUBLIC,說明是public方法,00 07是常量池第7項索引,表示方法名<init>00 08是常量池第8項索引,表示方法描述符()V00 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,分析這些指令。

  1. 讀入2a,查表得0x2A對應的指令爲aload_0,這個指令的含義是將第0個Slot中爲reference類型的本地變量推送到操做數棧頂。
  2. 讀入b7,查表得0xB7對應的指令爲invokespecial,這條指令的做用是以棧頂的reference類型的數據所指向的對象做爲方法接收者,調用此對象的實例構造器方法、private方法或者它的父類的方法。這個方法有一個u2類型的參數說明具體調用哪個方法,它指向常量池中的一個CONSTANT_Methodref_info類型常量,即此方法的方法符號引用。
  3. 讀入00 01,這是invokespecial的參數,查常量池得0x0001對應的常量爲實例構造器<init>方法的符號引用。
  4. 讀入b1,查表得0xB1對應的指令爲return,含義是返回此方法,而且返回值爲void。這條指令執行後,當前方法結束。

經過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…,整個分析過程是靠這篇博客的講解來的。

相關文章
相關標籤/搜索