常量池占据了class文件很大一部分数据,里面存放着各式各样的常量信息,包括:
数字和字符串常量、类和接口名、字段和方法名,等等
常量池实际上也是一个表,
但是有三点需要特别注意。
- 第一,表头给出的常量池大小比实际大1。假设表头给出的值是n,那么常
量池的实际大小是n–1。 - 第二,有效的常量池索引是1~n–1。0是无效索引,表示不指向任何常量。
- 第三,CONSTANT_Long_info和CONSTANT_Double_info各占两个位置。也就是说,如果常量池中存在这两种常量,实际的常量数量比n–1还要少,而且1~n–1的某些数也会变成无效索引。
由于常量池中存放的信息各不相同,所以每种常量的格式也不同。常量数据的第一字节是tag,用来区分常量类型。Java虚拟机规范给出的常量结构:
cp_info {
u1 tag;//常量类型
u1 info[];
}
Java虚拟机规范一共定义了14种常量。
const (
//没做
CONSTANT_MethodHandle = 15
CONSTANT_MethodType = 16
CONSTANT_InvokeDynamic = 18
//3个都是 指向 CONSTANT_Class_info和CONSTANT_NameAndType_info
CONSTANT_Fieldref = 9
CONSTANT_Methodref = 10
CONSTANT_InterfaceMethodref = 11
//指向Utf8
CONSTANT_String = 8
CONSTANT_Class = 7//类名
CONSTANT_NameAndType = 12//名字和描述符
//最基本的常量
CONSTANT_Utf8 = 1//字符串
CONSTANT_Integer = 3//4字节 有符号 更小的int更小的boolean、byte、short和char类型 也是它
CONSTANT_Float = 4//4字节有符号
CONSTANT_Long = 5//8字节 有符号
CONSTANT_Double = 6//8字节 有符号
)
这个的.class为例
package jvmgo.book.ch03;
public class ClassFileTest {
//常量池里面的类型都是 CONSTANT_Integer_info
public static final boolean FLAG = true;
public static final byte BYTE = 123;//
public static final char X = 'X';
public static final short SHORT = 12345;
public static final int INT = 123456789;//
public static final long LONG = 12345678901L;
public static final float PI = 3.14f;
public static final double E = 2.71828;
public static void main(String[] args) throws RuntimeException {
System.out.println("Hello, World!");
}
}
CONSTANT_Integer_info
4字节存储整数常量,其结构定义如下:
CONSTANT_Integer_info {
u1 tag;//3
u4 bytes;
}
CONSTANT_Integer_info正好可以容纳一个Java的int型常量,但实际上比int更小的boolean、byte、short和char类型的常量也放在CONSTANT_Integer_info中
CONSTANT_Float_info
使用4字节存储IEEE754单精度浮点数常量
CONSTANT_Float_info {
u1 tag;//4
u4 bytes;
}
CONSTANT_Long_info
8字节存储整数常量,结构如下:
CONSTANT_Long_info {
u1 tag;//5
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info
最后一个数字常量是CONSTANT_Double_info,使用8字节存
储IEEE754双精度浮点数,结构如下:
CONSTANT_Double_info {
u1 tag;//6
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Utf8_info
CONSTANT_Utf8_info常量里放的是MUTF-8编码的字符串,
结构如下:
CONSTANT_Utf8_info {
u1 tag;//1
u2 length;
u1 bytes[length];
}
注意,字符串在class文件中是以MUTF-8(Modified UTF-8)方式编码的。MUTF-8编码方式和UTF-8大致相同,但并不兼容。差别有两点:
一是null字符(代码点U+0000)会被编码成2字节:0xC0、0x80;
二是补充字符(Supplementary Characters,代码点大于U+FFFF的Unicode字符)是按UTF-16拆分为代理对(Surrogate Pair)分别编码的。
Java序列化机制也使用了MUTF-8编码。java.io.DataInput和java.io.DataOutput接口分别定义了readUTF()和writeUTF()方法,可以读写MUTF-8编码的字符串。
字段名、字段描述符等就是以字符串的形式存储在class文件中的,如字段PI对应的
CONSTANT_Utf8_info常量
CONSTANT_String_info
表示java.lang.String字面量,结构如下:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
可以看到,CONSTANT_String_info本身并不存放字符串数据,只存了常量池索引,这个索引指向一个CONSTANT_Utf8_info常量
CONSTANT_Class_info
表示类或者接口的符号引用, 和上一个差不多,
类和超类索引,接口表中的接口索引 指向的都是CONSTANT_Class_info常量。
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_NameAndType_info
给出字段或方法的名称和描述符。
其结构如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;//字段或方法名
u2 descriptor_index;//描述符
}
name_index
和descriptor_index
都是常量池索引,指向CONSTANT_Utf8_info
常量。
Java虚拟机规范定义了一种简单的语法来描述字段和方法,可以根据下面的规则生成描述符descriptor_index
- 1)类型描述符。
①基本类型
基本类型 | 描述符 |
---|---|
byte | B |
short | S |
char | C |
int | I |
long | J |
float | F |
double | D |
②引用类型的描述符是L+类的完全限定名+分号。
③数组类型的描述符是[+数组元素类型描述符。
- 2)字段描述符=字段类型的描述符。
- 3)方法描述符=(分号分隔的参数类型描述符)+返回值类型描述符
其中void返回值由单个字母V表示。
CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info
表示字段符号引用,普通(非接口)方法符号引用,接口方法符号引用。这三
种常量结构一模一样,
Class
NameAndType
加在一起可以唯一确定一个字段或者方法。
(类全名+成员名+描述符 = 唯一成员)
Java是不能定义多个同名字段的,哪怕它们的类型各不相同。这只是Java语法的限制而已,从class文件的层面来看,是完全可以支持这点的。
CONSTANT_XXXXref_info {
u1 tag;
u2 class_index;//指向CONSTANT_Class_info
u2 name_and_type_index;//指向CONSTANT_NameAndType_info
}
可以把常量池中的常量分为两类:
- 字面量(literal):
数字 字符串符号引用(symbolic reference):
类和接口名、字段和方法信息,都是通过索引直接或间接指向CONSTANT_Utf8_info常量