Class文件结构——常量池

  文章参考虚拟机规范Chapter 4. The class File Format,这篇文章只说常量池相关部分。常量池中主要存放两大类常量:字面量(Literal)和 符号引用(Symbolic References)。字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的全限定名(Full Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

  常量池中的每一项都是一个表,在 JDK1.7 之后共有 14 种结构各不相同的表结构数据,他们都有一个共同点,表开始的第一位是一个 u1 类型的标志位 tag,代表当前这个常量属于哪种常量类型。依旧参考规范

类型 tag标志 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 长整型字面量
CONSTANT_Double_info 双精度浮点型字面量
CONSTANT_Class_info 类或接口的符号引用
CONSTANT_String_info 字符串类型字面量
CONSTANT_Fieldref_info 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MothodType_info 16 标志方法类型
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

  描述符本质其实也就是是代表字段或方法类型的字符串,只不过它不会直接存储这个字符串的值,只会指向 CONSTANT_utf8_info 类型的字符串字面量的索引。

举个例子,有下面一个简单的类,查看其字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {

private int name;

public int getHash() {
return hashCode();
}

public int getName() {
return name;
}

public void setName(int name) {
this.name = name;
}
}

使用 javac Person.java编译,使用 javap -verbose Person 查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Classfile /Users/peihuan/workspace/webTest/src/main/java/Person.class
Last modified 2020-7-26; size 413 bytes
MD5 checksum 3b212604e8709ce491c1482b31c6eb32
Compiled from "Person.java"
public class Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#19 // java/lang/Object."<init>":()V
#2 = Methodref #5.#20 // java/lang/Object.hashCode:()I
#3 = Fieldref #4.#21 // Person.name:I
#4 = Class #22 // Person
#5 = Class #23 // java/lang/Object
#6 = Utf8 name
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 getHash
#13 = Utf8 ()I
#14 = Utf8 getName
#15 = Utf8 setName
#16 = Utf8 (I)V
#17 = Utf8 SourceFile
#18 = Utf8 Person.java
#19 = NameAndType #8:#9 // "<init>":()V
#20 = NameAndType #24:#13 // hashCode:()I
#21 = NameAndType #6:#7 // name:I
#22 = Utf8 Person
#23 = Utf8 java/lang/Object
#24 = Utf8 hashCode
{
public Person();
descriptor: ()V
flags: 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 int getHash();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method java/lang/Object.hashCode:()I
4: ireturn
LineNumberTable:
line 6: 0

public int getName();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field name:I
4: ireturn
LineNumberTable:
line 10: 0

public void setName(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #3 // Field name:I
5: return
LineNumberTable:
line 14: 0
line 15: 5
}
SourceFile: "Person.java"

主要查看的是 Constant pool 这一项, #1 是方法的符号引用,方法符号引用结构如下

1
2
3
4
5
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}

除了tag还有两个字段,一个指向声明方法的类描述符 CONSTANT_Class_info 的索引项,一个指向名称及类型描述符 CONSTANT_NameAndType 的索引项。#1 指向了 #5 和 #19,我们再看 #5 ,它是 CONSTANT_Class_info ,其结构如下,只有一个指向其全限定名常量项的索引, #5 指向了 #23,就是一个普普通通的字符串了,其值java/lang/Object就是这个类的名字。

1
2
3
4
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}

再看 #19,这是个 CONSTANT_NameAndType, 其结构如下 ,一个是该方法名称常量项的索引,一个是该方法常量描述符的索引。#19 指向了 #8 和 #9,#8 是方法名,是实例的构造器,也就是构造方法的名字,#9 ()V 是描述符,意思是,返回值是 void,没有参数。

{
1
2
3
4
    u1 tag;
u2 name_index;
u2 descriptor_index;
}

具体的描述符标识字符含义查看表4.2。例如,这个方法 Object m(int i, double d, Thread t) {..} 的描述符就是(IDLjava/lang/Thread;)Ljava/lang/Object;