文章参考虚拟机规范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
5
长整型字面量
CONSTANT_Double_info
6
双精度浮点型字面量
CONSTANT_Class_info
7
类或接口的符号引用
CONSTANT_String_info
8
字符串类型字面量
CONSTANT_Fieldref_info
9
字段的符号引用
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;