手動(dòng)翻譯class文件

一步步翻譯class,探究class文件結(jié)構(gòu)。
自己去一點(diǎn)點(diǎn)把class文件格式化,是一件非常有意思的事情。

1. 準(zhǔn)備

用來測(cè)試的java類:

public class Test {
    public static final String s = "good";

    int i;

    public void set(int i) {
        this.i = i;
    }
}

這樣一個(gè)簡單的java類文件,通過javac工具編譯成class后都包含什么東西呢?



用記事本打開Test.class,得到如下:

cafe babe 0000 0034 0018 0a00 0400 1309
0003 0014 0700 1507 0016 0100 0173 0100
124c 6a61 7661 2f6c 616e 672f 5374 7269
6e67 3b01 000d 436f 6e73 7461 6e74 5661
6c75 6508 0017 0100 0169 0100 0149 0100
063c 696e 6974 3e01 0003 2829 5601 0004
436f 6465 0100 0f4c 696e 654e 756d 6265
7254 6162 6c65 0100 0373 6574 0100 0428
4929 5601 000a 536f 7572 6365 4669 6c65
0100 0954 6573 742e 6a61 7661 0c00 0b00
0c0c 0009 000a 0100 0454 6573 7401 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
0100 0467 6f6f 6400 2100 0300 0400 0000
0200 1900 0500 0600 0100 0700 0000 0200
0800 0000 0900 0a00 0000 0200 0100 0b00
0c00 0100 0d00 0000 1d00 0100 0100 0000
052a b700 01b1 0000 0001 000e 0000 0006
0001 0000 0001 0001 000f 0010 0001 000d
0000 0022 0002 0002 0000 0006 2a1b b500
02b1 0000 0001 000e 0000 000a 0002 0000
0007 0005 0008 0001 0011 0000 0002 0012

最早的時(shí)候我看見這一堆,絕對(duì)要驚呼亂碼了。接下來就要把這些16進(jìn)制的字節(jié)一步步翻譯成我們看得懂的東西,從而去探究class的文件結(jié)構(gòu)。
假如還看過一點(diǎn)jvm相關(guān)知識(shí),翻譯著翻譯著會(huì)心一笑,原來如此。

1.1 javap工具

因?yàn)橐骄?6進(jìn)制字節(jié)形式的class文件的具體結(jié)構(gòu),肯定是自己去把16進(jìn)制字節(jié)一點(diǎn)點(diǎn)翻譯過來會(huì)比較印象深刻。
使用javap工具呢,可以把class文件直接翻譯成我們看得懂的英文的形式。這樣我們也可以用來作為參照,看自己是否翻譯對(duì)了。還能省去把16進(jìn)制字節(jié)轉(zhuǎn)換成英文utf-8字符串的步驟。

javap -verbose Test.class
Classfile /Users/lanzry/Documents/java/test/Test.class
  Last modified 2018年3月17日; size 336 bytes
  MD5 checksum b4674caa94520a1eeb8363c79b349ef2
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 53
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #3                          // Test
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 2, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#20         // Test.i:I
   #3 = Class              #21            // Test
   #4 = Class              #22            // java/lang/Object
   #5 = Utf8               s
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               ConstantValue
   #8 = String             #23            // good
   #9 = Utf8               i
  #10 = Utf8               I
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               set
  #16 = Utf8               (I)V
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #11:#12        // "<init>":()V
  #20 = NameAndType        #9:#10         // i:I
  #21 = Utf8               Test
  #22 = Utf8               java/lang/Object
  #23 = Utf8               good
{
  public static final java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String good

  int i;
    descriptor: I
    flags: (0x0000)

  public Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public void set(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field i:I
         5: return
      LineNumberTable:
        line 7: 0
        line 8: 5
}
SourceFile: "Test.java"

2. 手動(dòng)格式化

這樣的文件,就像沒有標(biāo)點(diǎn)符號(hào)的文言文,令人生畏。我要把它格式化。

# 魔數(shù)
cafe babe 
# java版本號(hào)(換算成十進(jìn)制=52,即java8)
0000 0034 

# 常量池大?。〒Q算=24,常量池第0項(xiàng)空著,所以共23個(gè))
0018 
# 常量池
# 16進(jìn)制是4位,8位是一個(gè)字節(jié),所以兩個(gè)數(shù)字是一字節(jié)
# 第一個(gè)字節(jié)是常量的類型,后面跟著該類型常量的屬性或者值
0a 0004 0013
09 0003 0014 
07 0015 
07 0016 
01 0001 73 
01 0012 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b
01 000d 436f 6e73 7461 6e74 5661 6c75 65
08 0017 
01 0001 69 
01 0001 49 
01 0006 3c69 6e69 743e 
01 0003 2829 56
01 0004 436f 6465 
01 000f 4c 696e 654e 756d 6265 7254 6162 6c65 
01 0003 73 6574 
01 0004 2849 2956
01 000a 536f 7572 6365 4669 6c65
01 0009 5465 7374 2e6a 6176 61 
0c 000b 000c
0c 0009 000a 
01 0004 5465 7374
01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374
01 0004 676f 6f64

# 當(dāng)前類的訪問修飾符
0021 
# 當(dāng)前類的索引,指向常量池第3項(xiàng)
0003
# 父類索引
0004
# 接口個(gè)數(shù)
0000

# 字段表(字段數(shù)量開頭,兩個(gè)字段)
0002 
0019 0005 0006 0001 0007 00000002 0008
0000 0009 000a 0000 

# 方法表
0002
# 方法1
0001 000b 000c 0001 000d 0000001d 0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01
# 方法2
0001 000f 0010 0001 000d 00000022 0002 0002 0000 0006 2a1b b500
02b1 0000 0001 000e 0000 000a 0002 0000 0007 0005 0008 

# 屬性表
0001 
0011 00000002 0012

大功告成,把一個(gè)class文件,按自己知道的去格式化出來了,這樣就清爽很多了。
自己把class文件整理一遍,真是能非常加深印象?。?br> 你看,一開始是魔數(shù),版本號(hào),常量池,類的訪問修飾符,類索引,父類索引,接口索引,字段表,方法表,屬性表,over!就這些,非常規(guī)整!

當(dāng)然會(huì)看這篇文章的小伙伴,證明你不太明白如何這樣整理。參照我接下來的知識(shí)就OK了!

接下來,就要看看是如何格式化出來的了?。?!

3. 相關(guān)的java虛擬機(jī)規(guī)范

下面會(huì)給出參考Java9的虛擬機(jī)規(guī)范的相關(guān)知識(shí)點(diǎn),會(huì)附上原英文鏈接。

3.1 class文件結(jié)構(gòu)

The ClassFile Structure

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

左邊一列是u1、u2、u3、u4和_info。
u1、u2是什么呢?其實(shí)是無符號(hào)數(shù),后面數(shù)字表示幾個(gè)字節(jié)。比如u1表示一個(gè)字節(jié)的無符號(hào)數(shù)。比如上述第一個(gè)u4 magic,意思是class文件結(jié)構(gòu)第一個(gè)是一個(gè)由4個(gè)字節(jié)表示的叫做“魔數(shù)”的東西。然后接下來兩個(gè)字節(jié)的minor version,2個(gè)字節(jié)的major version...
_info表示這是一個(gè)表。class文件中有很多表,每個(gè)表有不同的結(jié)構(gòu)。上述我們能看見的就有常量池表、字段表、方法表等等。表其實(shí)就是一個(gè)相同數(shù)據(jù)的集合,和列表的意思一樣,每種表的結(jié)構(gòu)是不一樣的,具體碰到的時(shí)候再去研究。

接下來,就按這張圖的順序,將以上16進(jìn)制的數(shù)據(jù)一一解釋成我們能看的懂的方式。

3.2 常量池

Constant pool tags

Constant Type Value class file Java SE
CONSTANT_Class 7 45.3 1.0.2
CONSTANT_Fieldref 9 45.3 1.0.2
CONSTANT_Methodref 10 45.3 1.0.2
CONSTANT_InterfaceMethodref 11 45.3 1.0.2
CONSTANT_String 8 45.3 1.0.2
CONSTANT_Integer 3 45.3 1.0.2
CONSTANT_Float 4 45.3 1.0.2
CONSTANT_Long 5 45.3 1.0.2
CONSTANT_Double 6 45.3 1.0.2
CONSTANT_NameAndType 12 45.3 1.0.2
CONSTANT_Utf8 1 45.3 1.0.2
CONSTANT_MethodHandle 15 51.0 7
CONSTANT_MethodType 16 51.0 7
CONSTANT_InvokeDynamic 18 51.0 7
CONSTANT_Module 19 53.0 9
CONSTANT_Package 20 53.0 9

常量池的類型一共以上這些,他們的結(jié)構(gòu)也各不相同,全部列出來太多了。大家有興趣可以自己去官方文檔了解,走各個(gè)小節(jié)的英文文檔入口。

這里把我們翻譯的部分的幾個(gè)類型列一下,一共是0a、09、07、01、08、0c這幾個(gè)。

3.2.1 The CONSTANT_Fieldref_info, CONSTANT_Methodref_info

16進(jìn)制 10進(jìn)制 對(duì)應(yīng) 意思
0a 10 CONSTANT_Methodref 方法符號(hào)引用
09 9 CONSTANT_Fieldref_info 字段符號(hào)引用

CONSTANT_Methodref_info

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

0a、09分別是方法和字段,二者的結(jié)構(gòu)一模一樣,所以一起寫。

例如方法符號(hào)引用的原文:0a 0004 0013
按上述結(jié)構(gòu)對(duì)應(yīng)起來,0a表示常量類型(tag),0004表示該方法所在類的索引是常量池第4個(gè),0013表示該方法的NameAndType屬性的索引位置是常量池第19個(gè)(16進(jìn)制的13)。

3.2.2 CONSTANT_Class

16進(jìn)制 10進(jìn)制 對(duì)應(yīng) 意思
07 7 CONSTANT_Class 類符號(hào)引用

The CONSTANT_Class_info Structure

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

例:07 0015
類型是7,表示類符號(hào)引用,類名描述符是常量池21(16進(jìn)制的15)項(xiàng)。
再看常量池21項(xiàng)是這樣的:01 0004 5465 7374
具體什么意思呢?接下去

3.2.3 CONSTANT_Utf8_info Structure

16進(jìn)制 10進(jìn)制 對(duì)應(yīng) 意思
01 7 CONSTANT_Utf8_info utf-8字符串

The CONSTANT_Utf8_info Structure

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

前一小節(jié)例子:01 0004 5465 7374
表示3個(gè)字節(jié)長的utf-8類型數(shù)據(jù),那么5465 7374這三個(gè)字節(jié)是什么意思?
[16進(jìn)制到文本字符串的轉(zhuǎn)換,在線實(shí)時(shí)轉(zhuǎn)換]

很明顯了,就是我們的類Test.java,類名就是Test。

3.2.4 The CONSTANT_String_info Structure

16進(jìn)制 10進(jìn)制 對(duì)應(yīng) 意思
08 7 CONSTANT_String_info String類型常量

CONSTANT_String_info Structure

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

例:08 0017

0017恰好是23項(xiàng),01 0004 676f 6f64,我們?cè)偃マD(zhuǎn)換一下:
就是我們測(cè)試?yán)又械某A孔址甮ood了。

3.2.6 The CONSTANT_NameAndType_info Structure

16進(jìn)制 10進(jìn)制 對(duì)應(yīng) 意思
0c 12 CONSTANT_NameAndType_info NameAndType類型

CONSTANT_NameAndType_info Structure

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}

例:0c 000b 000c
類型是NameAndType,名稱索引在第11項(xiàng),描述索引是第12項(xiàng)。

常量池就介紹到這里,其他的常量池常量類型,大家就可以遇到的時(shí)候去官方文檔The Constant Pool中查找了。

3.3 字段表

Fields

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

access_flags:訪問修飾符

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; usable only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.
ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ENUM 0x4000 Declared as an element of an enum.

name_index、descriptor_index,依舊是常量池的索引。

attribute_info,是一個(gè)屬性表。依舊會(huì)是屬性數(shù)量,然后屬性的列表的形式。后文再介紹。

3.4 方法表

Methods

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

和字段表差別不大。字段和方法的訪問修飾符還是有所差別的,下面列出來:

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5).
ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE 0x0040 A bridge method, generated by the compiler.
ACC_VARARGS 0x0080 Declared with variable number of arguments.
ACC_NATIVE 0x0100 Declared native; implemented in a language other than the Java programming language.
ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided.
ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.

還是舉個(gè)例子,做一次翻譯:

# 方法1
0001 000b 000c 0001 000d 0000001d 0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01

0001:ACC_PUBLIC
000b:方法名 = 常量池#11 = Utf8 = <init>(也就是默認(rèn)的構(gòu)造方法)
000c:方法描述 = 常量池#12 = Utf8 = ()V
括號(hào)內(nèi)空,表示沒有參數(shù);V表示返回值是void。這部分的文檔見Method Descriptors。
0001:表示屬性表只有一個(gè)屬性。

屬性表

000d 
0000001d 
0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01

000d:#13 = Utf8 = Code,表示是一個(gè)Code屬性
The Code Attribute

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

看這個(gè)就很有意思了,這里面像發(fā)現(xiàn)寶藏一樣。比如max_stack、max_locals是不是就是操作數(shù)棧、局部變量表的最大大小呢?之類,很有意思。
我們來翻譯一遍:

# Code
000d 
# 長度29
0000001d
# max_stack=1 
0001 
# max_locals=1
0001 
# code_length=5
00000005 
2ab7 0001 b1
# 沒有異常,所以異常表是0
0000
# 后面又接著1個(gè)屬性表
0001 
# 14 = Utf8 = LineNumberTable屬性
000e 
# LineNumberTable長度
00000006 
# start_pc這個(gè)就很容易懂了
0001 
# start_pc
0000
# lineNumber
0001

簡單介紹一下The LineNumberTable Attribute,就是字節(jié)碼對(duì)應(yīng)源代碼的行號(hào),這樣調(diào)試的時(shí)候可以根據(jù)這個(gè)找到當(dāng)前執(zhí)行到了哪一行。

LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number; 
    } line_number_table[line_number_table_length];
}

3.5 屬性表

Attributes

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

把最后多余出來的屬性表作為例子:

# 屬性表
0001 
0011 00000002 0012

該屬性名字是常量池第17項(xiàng)(16進(jìn)制的11),01 000a 536f 7572 6365 4669 6c65,01類型是utf-8字符串,轉(zhuǎn)換一下:SourceFile

屬性表里面又分很多屬性類別,定位到屬性名稱后,再去官方查找對(duì)應(yīng)文檔即可。

The SourceFile Attribute

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

值是長度是2個(gè)字節(jié)。最后一項(xiàng)sourcefile_index索引,0012,對(duì)應(yīng)常量池18項(xiàng):01 0009 5465 7374 2e6a 6176 61 ,轉(zhuǎn)換一下:Test.java
意思就是記錄源文件名字是Test.java。

這里列出所有屬性的類型:
Table 4.7-A. Predefined class file attributes (by section)

Attribute Section class file Java SE
ConstantValue §4.7.2 45.3 1.0.2
Code §4.7.3 45.3 1.0.2
StackMapTable §4.7.4 50.0 6
Exceptions §4.7.5 45.3 1.0.2
InnerClasses §4.7.6 45.3 1.1
EnclosingMethod §4.7.7 49.0 5.0
Synthetic §4.7.8 45.3 1.1
Signature §4.7.9 49.0 5.0
SourceFile §4.7.10 45.3 1.0.2
SourceDebugExtension §4.7.11 49.0 5.0
LineNumberTable §4.7.12 45.3 1.0.2
LocalVariableTable §4.7.13 45.3 1.0.2
LocalVariableTypeTable §4.7.14 49.0 5.0
Deprecated §4.7.15 45.3 1.1
RuntimeVisibleAnnotations §4.7.16 49.0 5.0
RuntimeInvisibleAnnotations §4.7.17 49.0 5.0
RuntimeVisibleParameterAnnotations §4.7.18 49.0 5.0
RuntimeInvisibleParameterAnnotations §4.7.19 49.0 5.0
RuntimeVisibleTypeAnnotations §4.7.20 52.0 8
RuntimeInvisibleTypeAnnotations §4.7.21 52.0 8
AnnotationDefault §4.7.22 49.0 5.0
BootstrapMethods §4.7.23 51.0 7
MethodParameters §4.7.24 52.0 8
Module §4.7.25 53.0 9
ModulePackages §4.7.26 53.0 9
ModuleMainClass §4.7.27 53.0 9

到這里就算差不多了。簡單地了解一下簡單java類的class文件結(jié)構(gòu),就已經(jīng)足夠了。以后即使是復(fù)雜的java類,其實(shí)我們也能明白結(jié)構(gòu)是怎么樣。即使不知道,還有官方文檔The Java? Virtual Machine Specification Java SE 9 Edition啊 哈哈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容