Java Class文件结构分析

时间:2021-04-14 14:05:17

一、简单介绍

    Java编译好后的文件是Class文件,该文件在虚拟机上运行,只有虚拟机能够识别。所以编译后的Class文件不依赖于具体的平台,具有跨平台的特性,但是依赖于虚拟机,而且不需要连接。很多语言,包括Java,Python,都可以编译成Class文件。Class文件与EXE文件相比,比较紧凑,不需要填充和对齐。代码在方法区的Code属性中。
    将Person.java代码使用javac Person.java编译:

import java.util.Scanner;
public class Person{
private String name = "xiaoming";
private int age = 23;
public int getAge(){
return age;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}

public void input(){
Scanner scanner = new Scanner(System.in);
}
}

    然后使用以下命令查看class文件:

xxd -g 1 Person.class

    可以得到:

00000000: ca fe ba be 00 00 00 33 00 26 09 00 08 00 18 0a .......3.&......
00000010: 00 09 00 19 08 00 1a 09 00 08 00 1b 07 00 1c 09 ................
00000020: 00 1d 00 1e 0a 00 05 00 1f 07 00 20 07 00 21 01 ........... ..!.
00000030: 00 04 6e 61 6d 65 01 00 12 4c 6a 61 76 61 2f 6c ..name...Ljava/l
00000040: 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 03 61 67 ang/String;...ag
00000050: 65 01 00 01 49 01 00 06 67 65 74 41 67 65 01 00 e...I...getAge..
00000060: 03 28 29 49 01 00 04 43 6f 64 65 01 00 0f 4c 69 .()I...Code...Li
00000070: 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 06 neNumberTable...
00000080: 3c 69 6e 69 74 3e 01 00 16 28 4c 6a 61 76 61 2f <init>...(Ljava/
00000090: 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 49 29 56 01 lang/String;I)V.
000000a0: 00 05 69 6e 70 75 74 01 00 03 28 29 56 01 00 0a ..input...()V...
000000b0: 53 6f 75 72 63 65 46 69 6c 65 01 00 0b 50 65 72 SourceFile...Per
000000c0: 73 6f 6e 2e 6a 61 76 61 0c 00 0c 00 0d 0c 00 12 son.java........
000000d0: 00 15 01 00 08 78 69 61 6f 6d 69 6e 67 0c 00 0a .....xiaoming...
000000e0: 00 0b 01 00 11 6a 61 76 61 2f 75 74 69 6c 2f 53 .....java/util/S
000000f0: 63 61 6e 6e 65 72 07 00 22 0c 00 23 00 24 0c 00 canner.."..#.$..
00000100: 12 00 25 01 00 06 50 65 72 73 6f 6e 01 00 10 6a ..%...Person...j
00000110: 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 ava/lang/Object.
00000120: 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 ..java/lang/Syst
00000130: 65 6d 01 00 02 69 6e 01 00 15 4c 6a 61 76 61 2f em...in...Ljava/
00000140: 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b 01 io/InputStream;.
00000150: 00 18 28 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 ..(Ljava/io/Inpu
00000160: 74 53 74 72 65 61 6d 3b 29 56 00 21 00 08 00 09 tStream;)V.!....
00000170: 00 00 00 02 00 02 00 0a 00 0b 00 00 00 02 00 0c ................
00000180: 00 0d 00 00 00 03 00 01 00 0e 00 0f 00 01 00 10 ................
00000190: 00 00 00 1d 00 01 00 01 00 00 00 05 2a b4 00 01 ............*...
000001a0: ac 00 00 00 01 00 11 00 00 00 06 00 01 00 00 00 ................
000001b0: 06 00 01 00 12 00 13 00 01 00 10 00 00 00 47 00 ..............G.
000001c0: 02 00 03 00 00 00 1b 2a b7 00 02 2a 12 03 b5 00 .......*...*....
000001d0: 04 2a 10 17 b5 00 01 2a 2b b5 00 04 2a 1c b5 00 .*.....*+...*...
000001e0: 01 b1 00 00 00 01 00 11 00 00 00 1a 00 06 00 00 ................
000001f0: 00 08 00 04 00 03 00 0a 00 04 00 10 00 09 00 15 ................
00000200: 00 0a 00 1a 00 0b 00 01 00 14 00 15 00 01 00 10 ................
00000210: 00 00 00 28 00 03 00 02 00 00 00 0c bb 00 05 59 ...(...........Y
00000220: b2 00 06 b7 00 07 4c b1 00 00 00 01 00 11 00 00 ......L.........
00000230: 00 0a 00 02 00 00 00 0e 00 0b 00 0f 00 01 00 16 ................
00000240: 00 00 00 02 00 17 ......

    Class文件的整体结构如下:

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

    下面对整个文件进行解析:

数据结构 内容 含义
magic ca fe ba be 表示class文件,不以后缀作为判断依据
minor_version 00 00 次版本号0
major_version 00 33 主版本号51,表示JDK1.7.0
constant_pool_count 00 26 总共有0x26 - 1 = 37个常量池项,且序号范围是1-37,序号0用于表示没有使用常量池的特殊情况。

二、常量池

    常量池可以有多种数据类型,很多Class文件的其他项都引用常量池,因而常量池保存了很重要的信息,一般都比较大。总共有37项。常量池的作用:存储变量名和方法名,类全限定名(/分割),方法描述符,类型信息等。
    第1项是CONSTANT_Fieldref_info

数据结构 内容 含义
tag 0x09 表示CONSTANT_Fieldref_info
CONSTANT_Class_info index 00 08 Person
CONSTANT_NameAndType index 00 18 age : I

    第2项是 CONSTANT_Methodref_info,结构与CONSTANT_Fieldref_info类似:

数据结构 内容 含义
tag 0x0a 表示CONSTANT_Methodref_info
CONSTANT_Class_info index 00 09 java/lang/Object
CONSTANT_NameAndType index 00 19 “<init>”:()V

    第3项是CONSTANT_String_info

数据结构 内容 含义
tag 0x08 表示CONSTANT_String_info
CONSTANT_Utf8_info index 00 1a xiaoming

    第4项是CONSTANT_Fieldref_info

数据结构 内容 含义
tag 0x09 表示CONSTANT_Fieldref_info
CONSTANT_Class_info index 00 08 Person
CONSTANT_NameAndType index 00 1b name:Ljava/lang/String;

    第5项是CONSTANT_Class_info

数据结构 内容 含义
tag 0x07 表示CONSTANT_Class_info
CONSTANT_Utf8_info index 00 1c java/util/Scanner

    第6项是CONSTANT_Fieldref_info

数据结构 内容 含义
tag 0x09 表示CONSTANT_Fieldref_info
CONSTANT_Class_info index 00 1d java/lang/System
CONSTANT_NameAndType index 00 1e in:Ljava/io/InputStream;

    第7项是 CONSTANT_Methodref_info,结构与CONSTANT_Fieldref_info类似:

数据结构 内容 含义
tag 0x0a 表示CONSTANT_Methodref_info
CONSTANT_Class_info index 00 05 java/util/Scanner
CONSTANT_NameAndType index 00 1f “<init>”:(Ljava/io/InputStream;)V

    第8项是CONSTANT_Class_info

数据结构 内容 含义
tag 0x07 表示CONSTANT_Class_info
CONSTANT_Utf8_info index 00 20 Person

    第9项是CONSTANT_Class_info

数据结构 内容 含义
tag 0x07 表示CONSTANT_Class_info
CONSTANT_Utf8_info index 00 21 java/lang/Object

    第10项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 04 4个字节
byte 6e 61 6d 65 name

    第11项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 12 18个字节
byte 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b Ljava/lang/String;

    第12项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 03
byte 61 67 65 age

    第13项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 01
byte 49 I

    第14项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 06
byte 67 65 74 41 67 65 getAge

    第15项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 03
byte 28 29 49 ()I

    第16项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 04
byte 43 6f 64 65 Code

    第17项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 0f
byte 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 LineNumberTable

    第18项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 06
byte 3c 69 6e 69 74 3e <init>

    第19项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 16
byte 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 49 29 56 (Ljava/lang/String;I)V

    第20项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 05
byte 69 6e 70 75 74 input

    第21项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 03
byte 28 29 56 ()V

    第22项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 0a
byte 53 6f 75 72 63 65 46 69 6c 65 SourceFile

    第23项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 0b
byte 50 65 72 73 6f 6e 2e 6a 61 76 61 Person.java

    第24项是CONSTANT_NameAndType_info

数据结构 内容 含义
tag 0x0c 表示CONSTANT_NameAndType_info
name index 00 0b age
descriptor index 00 0d I

    第25项是CONSTANT_NameAndType_info

数据结构 内容 含义
tag 0x0c 表示CONSTANT_NameAndType_info
name index 00 12 <init>
descriptor index 00 15 ()V

    第26项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 08
byte 78 69 61 6f 6d 69 6e 67 xiaoming

    第27项是CONSTANT_NameAndType_info

数据结构 内容 含义
tag 0x0c 表示CONSTANT_NameAndType_info
name index 00 0a name
descriptor index 00 0b Ljava/lang/String;

    第28项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 11
byte 6a 61 76 61 2f 75 74 69 6c 2f 53 63 61 6e 6e 65 72 java/util/Scanner

    第29项是CONSTANT_Class_info

数据结构 内容 含义
tag 0x07 表示CONSTANT_Class_info
class name index 00 22 java/lang/System

    第30项是CONSTANT_NameAndType_info

数据结构 内容 含义
tag 0x0c 表示CONSTANT_NameAndType_info
name index 00 23 in
descriptor index 00 24 Ljava/io/InputStream;

    第31项是CONSTANT_NameAndType_info

数据结构 内容 含义
tag 0x0c 表示CONSTANT_NameAndType_info
name index 00 12 <init>
descriptor index 00 25 (Ljava/io/InputStream;)V

    第32项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 06
byte 50 65 72 73 6f 6e Person

    第33项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 10
byte 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 java/lang/Object

    第34项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 10
byte 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d java/lang/System

    第35项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 02
byte 69 6e in

    第36项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 15
byte 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b Ljava/io/InputStream;

    第37项是CONSTANT_Utf8_info

数据结构 内容 含义
tag 0x01 表示CONSTANT_Utf8_info
length 00 18
byte 28 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b 29 56 (Ljava/io/InputStream;)V

    由此可以得到常量池中主要数据类型的关系:
Java Class文件结构分析

三、其他部分

数据结构 内容 含义
access_flags 00 21 本类为public
this_class 00 08 常量池索引。Person
super_class 00 09 java/lang/Object
interfaces_count 00 00
interfaces
fields_count 00 02

    fields :

  • 第一项name java.lang.String:
数据结构 内容 含义
access_flags 0002 private
name_index 00 0a name
descriptor index 00 0b Ljava/lang/String;
attribute_counts 00 00 零项
attribute_info
  • 第二项age I:
数据结构 内容 含义
access_flags 0002 private
name_index 00 0c age
descriptor index 00 0d I
attribute_counts 00 00 零项
attribute_info

    methods_count : 00 03。method的结构与field类似。

  • 第一项getAge().I:
数据结构 内容 含义
access_flags 0001 public
name_index 00 0e getAge
descriptor index 00 0f ()I
attribute_counts 00 01 一项
attribute_info 一项

    attribute_info的信息如下:

数据结构 内容 含义
attribute_name_index 00 10 属性为Code
attribute_length 00 00 00 1d 属性长度为29,表示下面有29个字节
max_stack 00 01
max_locals 00 01
code_length 00 00 00 05
code 2a b4 00 01 ac
exception_table_length 00 00
attributes_count 00 01
attribute_name_index 00 11 索引项17为LineNumberTable
attribute_length 00 00 00 06 表示下面有6个字节
line_number_table_length 00 01
line_number_table 00 00 00 06 前两个字节为字节码行号0,后两个字节码为Java源码行号6
  • 第二项public Person(String name, int age):
数据结构 内容 含义
access_flags 0001 public
name_index 00 12 <init>
descriptor index 00 13 (Ljava/lang/String;I)V
attribute_counts 00 01 一项
attribute_info 一项

    属性内容为

数据结构 内容 含义
attribute_name_index 00 10 属性为Code
attribute_length 00 00 00 47 71
max_stack 00 02
max_locals 00 03
code_length 00 00 00 1b 27
code 2a b7 00 02 2a 12 03 b5 00 04 2a 10 17 b5 00 01 2a 2b b5 00 04 2a 1c b5 00 01 b1
exception_table_length 00 00
attributes_count 00 01
attribute_name_index 00 11 属性为LineNumberTable
attribute_length 00 00 00 1a 26个字节
line_number_table_length 00 06
line_number_table 00 00 00 08 00 04 00 03 00 0a 00 04 00 10 00 09 00 15 00 0a 00 1a 00 0b 共有6项,每一项4个字节。前两个字节为字节码行号,后两个字节为Java源码行号
  • 第三项public void input():
数据结构 内容 含义
access_flags 0001 public
name_index 00 14 input
descriptor index 00 15 ()V
attribute_counts 00 01 一项
attribute_info 一项

    属性内容为

数据结构 内容 含义
attribute_name_index 00 10 属性为Code
attribute_length 00 00 00 28 40
max_stack 00 03
max_locals 00 02
code_length 00 00 00 0c 12
code bb 00 05 59 b2 00 06 b7 00 07 4c b1
exception_table_length 00 00
attributes_count 00 01
attribute_name_index 00 11
attribute_length 00 00 00 0a
line_number_table_length 00 02 有2项line_number_table
line_number_info 00 00 00 0e 00 0b 00 0f 前两个字节为字节码行号,后两个字节为Java源码行号

    从上述定义可以看出,常量池中的Fieldref和Methodref只是起引用作用。还需要在全局Fields和Methods中定义其访问属性,对于方法还要有Code属性来存储字节码指令。
    最后,本类SourceFile属性如下:

数据结构 内容 含义
attributes_count 00 01 本类只有一项属性
attribute_name_index 00 16 SourceFile
attribute_length 00 00 00 02
sourcefile_index 00 17 Person.java

四、使用javap命令反编译Class文件

    具体命令如下:

javap -verbose Person.class

    得到以下输出,它包含了Class文件的详细信息。

Classfile /home/jessin/Documents/Program/Java/Person.class
Last modified 2017-7-3; size 582 bytes
MD5 checksum 61bc2d8c4a18440e51dddf16acc70dfe
Compiled from "Person.java"
public class Person
SourceFile: "Person.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Fieldref #8.#24 // Person.age:I
#2 = Methodref #9.#25 // java/lang/Object."<init>":()V
#3 = String #26 // xiaoming
#4 = Fieldref #8.#27 // Person.name:Ljava/lang/String;
#5 = Class #28 // java/util/Scanner
#6 = Fieldref #29.#30 // java/lang/System.in:Ljava/io/InputStream;
#7 = Methodref #5.#31 // java/util/Scanner."<init>":(Ljava/io/InputStream;)V
#8 = Class #32 // Person
#9 = Class #33 // java/lang/Object
#10 = Utf8 name
#11 = Utf8 Ljava/lang/String;
#12 = Utf8 age
#13 = Utf8 I
#14 = Utf8 getAge
#15 = Utf8 ()I
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 <init>
#19 = Utf8 (Ljava/lang/String;I)V
#20 = Utf8 input
#21 = Utf8 ()V
#22 = Utf8 SourceFile
#23 = Utf8 Person.java
#24 = NameAndType #12:#13 // age:I
#25 = NameAndType #18:#21 // "<init>":()V
#26 = Utf8 xiaoming
#27 = NameAndType #10:#11 // name:Ljava/lang/String;
#28 = Utf8 java/util/Scanner
#29 = Class #34 // java/lang/System
#30 = NameAndType #35:#36 // in:Ljava/io/InputStream;
#31 = NameAndType #18:#37 // "<init>":(Ljava/io/InputStream;)V
#32 = Utf8 Person
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 in
#36 = Utf8 Ljava/io/InputStream;
#37 = Utf8 (Ljava/io/InputStream;)V
{
public int getAge();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field age:I
4: ireturn
LineNumberTable:
line 6: 0

public Person(java.lang.String, int);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #3 // String xiaoming
7: putfield #4 // Field name:Ljava/lang/String;
10: aload_0
11: bipush 23
13: putfield #1 // Field age:I
16: aload_0
17: aload_1
18: putfield #4 // Field name:Ljava/lang/String;
21: aload_0
22: iload_2
23: putfield #1 // Field age:I
26: return
LineNumberTable:
line 8: 0
line 3: 4
line 4: 10
line 9: 16
line 10: 21
line 11: 26

public void input();
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: new #5 // class java/util/Scanner
3: dup
4: getstatic #6 // Field java/lang/System.in:Ljava/io/InputStream;
7: invokespecial #7 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
10: astore_1
11: return
LineNumberTable:
line 14: 0
line 15: 11
}

    参考文献:《深入理解Java虚拟机》周志明