3.3 解析常量池

常量池占据了class文件很大一部分数据,里面存放着各式各样的常量信息,包括:
数字和字符串常量、类和接口名、字段和方法名,等等

常量池实际上也是一个表,


3.3 解析常量池_第1张图片
常量池是个表,表头u2 ,这里是64-1个表项

但是有三点需要特别注意。

  • 第一,表头给出的常量池大小比实际大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中


3.3 解析常量池_第2张图片
int INT = 123456789;

byte BYTE = 123;

short SHORT = 12345;

boolean FLAG = true;

char X = 'X';

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;
}
long LONG = 12345678901L;

CONSTANT_Double_info

最后一个数字常量是CONSTANT_Double_info,使用8字节存
储IEEE754双精度浮点数,结构如下:

CONSTANT_Double_info {
  u1 tag;//6
  u4 high_bytes;
  u4 low_bytes;
}
double E = 2.71828;

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常量


3.3 解析常量池_第3张图片
PI = 3.14f;

CONSTANT_String_info

表示java.lang.String字面量,结构如下:

CONSTANT_String_info {
  u1 tag;
  u2 string_index;
}

可以看到,CONSTANT_String_info本身并不存放字符串数据,只存了常量池索引,这个索引指向一个CONSTANT_Utf8_info常量

3.3 解析常量池_第4张图片
34就是52

指向utf8_info

CONSTANT_Class_info

表示类或者接口的符号引用, 和上一个差不多,
类和超类索引,接口表中的接口索引 指向的都是CONSTANT_Class_info常量。

CONSTANT_Class_info {
  u1 tag;
  u2 name_index;
}
this_class

55

CONSTANT_NameAndType_info

给出字段或方法的名称和描述符。

其结构如下:

CONSTANT_NameAndType_info {
  u1 tag;
  u2 name_index;//字段或方法名
  u2 descriptor_index;//描述符
}

name_indexdescriptor_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.3 解析常量池_第5张图片
  • 3)方法描述符=(分号分隔的参数类型描述符)+返回值类型描述符
    其中void返回值由单个字母V表示。
    3.3 解析常量池_第6张图片

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
}
System.out

java.lang.System

成员名字和描述符

可以把常量池中的常量分为两类:

  • 字面量(literal):
    数字 字符串
  • 符号引用(symbolic reference):
    类和接口名、字段和方法信息,都是通过索引直接或间接指向CONSTANT_Utf8_info常量


    3.3 解析常量池_第7张图片
    CONSTANT_Fieldref_info为例

你可能感兴趣的:(3.3 解析常量池)