class文件结构[3] 常量池
【参考链接】
《Java虚拟机原理图解》 1.2、class文件中的常量池http://blog.****.net/luanlouis/article/details/40148053
《Java虚拟机原理图解》 1.2.2、Class文件中的常量池详解(上)http://blog.****.net/luanlouis/article/details/39960815
《Java虚拟机原理图解》 1.2.3、Class文件中的常量池详解(下)http://blog.****.net/luanlouis/article/details/40301985
常量池中的内容大致上分为3类
1
当前类中出现的各种基本数据类型的数值。
CONSTANT_Utf8_info
这个又可以分为5类
1)
字符串字面量
注意char及char[]类型不会生成字符串字面量,JVM会使用后面的CONSTANT_Integer_info整数数值来表示。
以如下代码为例
JavaCode
1 |
package com.shadowfaxghh.test.b; |
字符串字面量有
2)
当前类/接口的全限定名
当前类的直接父类/父接口的全限定名
当前类所实现的所有接口的全限定名
其中全限定名是指
如Object类, 在.java源文件中的全限定名是java.lang.Object 。 而在.class文件中的全限定名是将点号替换成“/”, 即java/lang/Object 。
当前类所定义的成员变量的名称和描述符
其中 成员变量的描述符是指
1)void和基本数据类型的成员变量的描述符为
void和基本数据类型 |
描述符 |
void |
V |
byte |
B |
char |
C |
double |
D |
float |
F |
int |
I |
long |
J |
short |
S |
boolean |
Z |
基本上都是以类型的首字母变成大写来对应的,其中long和boolean是特例, long类型的描述符是J, boolean类型的描述符是Z 。
2) 引用类型的成员变量的描述符为
“L” + 类型的全限定名 + “;”
如Object类型的成员变量的描述符是:Ljava/lang/Object; ArrayList类型的成员变量的描述符是: Ljava/lang/ArrayList ;
3) 数组类型的成员变量的描述符为
若干个“[” + 数组中元素类型的对应字符串
int[]类型的对应字符串是: [I 。 int[][]类型的对应字符串是:[[I 。 Object[]类型的对应字符串是: [Ljava/lang/Object; 。 Object[][][]类型的对应字符串是:[[[Ljava/lang/Object;
当前类中所定义的成员方法的名称和描述符
其中 成员方法的描述符是指
(参数1类型 参数2类型 参数3类型 ...)返回值类型
举例说明如下
成员方法 |
描述符 |
int getSize() |
()I |
String toString() |
()Ljava/lang/String; |
void main(String[] args) |
([Ljava/lang/String;)V |
void wait() |
()V |
void wait(long timeout, int nanos) |
(JI)V |
boolean regionMatches(boolean ignoreCase, int toOffset, String other, int ooffset, int len) |
(ZILjava/lang/String;II)Z |
int read(byte[] b, int off, int len ) |
([BII)I |
Object[][] getObjectArray() |
()[[Ljava/lang/Object; |
以如下代码为例
Java Code
1 |
package com.test.b; |
其中
类/接口的全限定名有
成员变量和成员方法的名称和描述符有
其中成员变量和成员方法的名称和描述符,实际是用于常量池后面的Fields和Methods的引用
3)
当前类所引用的其他类/接口的全限定名
当前类所引用的其他类的成员变量的名称和描述符
当前类所引用的其他类的成员方法的名称和描述符
所谓引用,是指创建了其他类的对象,或者调用了其他类的成员变量或成员方法
依然以上面的代码为例
Java Code
1 |
package com.test.b; |
常量池中的相关项有
这些常量池项,其实是用于下面的Class、Fieldref、 Methodref、InterfaceMethodRef的引用,最终是用于常量池后面的Methods中的相关的指令字节码,如new、get、put、invoke
所以,如果Methods的指令字节码中并没有创建其他类的对象,或者调用其他类的成员变量或成员方法,则不会生成相关的常量池项
在上面代码的基础上,更改m1不进行初始化(会有默认值null),即
Java Code
1 |
package com.test.c; |
则指令字节码中就不会有new Date,则不会有Date的CONSTANT_Class_info(下面讲到),也不会有Date的CONSTANT_Utf8_info
4)
形式参数和局部变量的名称和描述符
这里需要注意两点
1、 非静态成员方法,会在局部变量表中默认添加一个局部变量this
2、 如之前所讲,如果局部变量只定义,未进行初始化,后续也未赋值使用,则会被删除掉。
依然以上面的代码为例
Java Code
1 |
package com.test.b; |
常量池中的相关项有
这些常量池项,其实是用于Methods中的局部变量表的引用
5)
属性相关的字符串
CONSTANT_Integer_info
CONSTANT_Long_info
CONSTANT_Float_info
CONSTANT_Double_info
对于long、float、double,只要是出现的常量值,都会有对应的常量池项
以如下代码为例
Java Code
1 |
package com.test.d; |
其使用时的字节码指令如下
其中integer的有些特殊
1、 只有final类型的integer才会生成常量池项
2、 会直接将数值整合到字节码指令中去(搞不懂为什么final类型的不引用常量池,那final类型的还生成常量池项干什么?)
3、 虽然.java源代码/java语言中支持byte,short, char, boolean类型, 但是.class文件/JVM中却不支持这几种类型, 这几种类型都当做int来对待。
表现在.class文件中就是, 这几种类型的数值在常量池中都为CONSTANT_Integer_info类型。
注意只是数值是CONSTANT_Integer_info类型,描述符还是各自有各自的,前文已有介绍。
以如下代码为例
Java Code
1 |
package com.test.e; |
常量池中的项为,其中小写字母b对应的整数值是98
字节码指令为
2
CONSTANT_String_info
对于字符串字面量,除了会生成一个上面的CONSTANT_Utf8_info,还是生成一个CONSANT_String_info,引用CONSTANT_Utf8_info
那为什么还要再多存储这么一项呢?这么做其实跟JVM对字符串的管理有关,有何作用详见后续文章
以上面最开始的代码为例
JavaCode
1 |
package com.shadowfaxghh.test.b; |
其对应的常量池项有
3
CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
CONSTANT_InterfaceMethodref_info
这些其实在讲CONSTANT_Utf8_info的时候就已经讲过了,就是对CONSTANT_Utf8_info的引用或多次引用
在执行字节码指令new、put、get、invoke时会引用到这些