符号引用
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用
·相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
·一个能间接定位到目标的句柄
·直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
·直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。
常量定义
“常量”在程序运行时,不会被修改的量。换言之,常量虽然是为了硬件、软件、编程语言服务,但是它并不是因为硬件、软件、编程语言而引入。
字面常量(直接常量):整型常量、字符常量、实型常量等。在; letter-spacing: 0pt; font-weight: normal; font-size: 12pt; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial;">; letter-spacing: 0pt; font-weight: normal; font-size: 12pt; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial;">-c
-verbose -classpath workspaceloc/[Math
Processing Error]{project_name}/bin
${java_type_name}在eclipse首先配置javap方便后续使用,参考下图:
packagecom.sunld;publicclassTest{
publicfinalinta = 1;
publicstaticintb;
publicfinalstaticintc=2;
}
使用javap查看编译后的class文件
编译后的class文件信息如下:
Classfile
/D:/Workspaces/eclipse_neon/TestJvm/bin/com/sunld/Test.class
Last modified 2017-6-7; size
362 bytes
MD5 checksum
3b07cb0016432f841b26eb9015c06c20
Compiled from
"Test.java"public class com.sunld.Test
minor version: 0
major version: 50
flags: ACC_PUBLIC,
ACC_SUPER
Constant
pool:
#1 = Class
#2
//
com/sunld/Test
#2 = Utf8
com/sunld/Test
#3 = Class
#4
//
java/lang/Object
#4 = Utf8
java/lang/Object
#5 = Utf8
a
#6 = Utf8
I
#7 = Utf8
ConstantValue
#8 =
Integer
1
#9 = Utf8
b
#10 = Utf8
c
#11 = Integer
2
#12 = Utf8
#13 = Utf8
()V
#14 = Utf8
Code
#15 = Methodref
#3.#16
//
V
#16 = NameAndType
#12:#13
//
"":()V
#17 = Fieldref
#1.#18
//
com/sunld/Test.a:I
#18 = NameAndType
#5:#6
//
a:I
#19 = Utf8
LineNumberTable
#20 = Utf8
LocalVariableTable
#21 = Utf8
this
#22 = Utf8
Lcom/sunld/Test;
#23 = Utf8
SourceFile
#24 = Utf8
Test.java
{
public final int
a;
descriptor:
I
flags:
ACC_PUBLIC, ACC_FINAL
ConstantValue:
int 1
public static int
b;
descriptor:
I
flags:
ACC_PUBLIC, ACC_STATIC
public static final int
c;
descriptor:
I
flags:
ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue:
int 2
public
com.sunld.Test();
descriptor:
()V
flags:
ACC_PUBLIC
Code:
stack=2,
locals=1, args_size=1
0:
aload_0
1:
invokespecial #15
//
Method V
4:
aload_0
5:
iconst_1
6:
putfield
#17
//
Field a:I
9:
return
LineNumberTable:
line
11: 0
line
12: 4
line
11: 9
LocalVariableTable:
Start
Length Slot
Name
Signature
0
。。。10
0
this
Lcom/sunld/Test;
}
SourceFile:
"Test.java"
可以看到对之前声明的属性编译后的显示,以及class文件常量池中存储的内容。
Class文件常量池
源码文件在编译之后生成class文件,虚拟机对于class文件的结构有严格的要求,class文件中的数据项参考下图:
常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic
References)
关于Class文件常量池会在后续的文章中详细介绍。Class文件常量表,来源于网络。
运行时常量池
当java文件被编译成class文件之后,就会生成Class文件常量池;jvm在执行某个类的时候,必须经过加载、连接、初始化
,而连接又包括验证、准备、解析三个阶段。(在JVM中,类的生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段)而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,方法。
常量池的优点
可以理解为缓存的概念。常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
字符串常量池
字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string
pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。 )。
在HotSpot VM里实现的string
pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot
VM的实例只有一份,被所有的类共享。
代码1
publicclass TestString2 {
private String str = "sun" +
"ld";
private
static String str1 = "sun1" + "ld1";
private
static final String str2 = "sun2" + "ld2";
private
final String str3 = "sun3" + "ld3";
}
Constant
pool:
#1 = Class
#2
//
com/sunld/TestString2
#2 = Utf8
com/sunld/TestString2
#3 = Class
#4
//
java/lang/Object
#4 = Utf8
java/lang/Object
#5 = Utf8
str
#6 = Utf8
Ljava/lang/String;
#7 = Utf8
str1
#8 = Utf8
str2
#9 = Utf8
ConstantValue
#10 = String
#11
//
sun2ld2
#11 = Utf8
sun2ld2
#12 = Utf8
str3
#13 = String
#14
//
sun3ld3
#14 = Utf8
sun3ld3
#15 = Utf8
#16 = Utf8
()V
#17 = Utf8
Code
#18 = String
#19
//
sun1ld1
#19 = Utf8
sun1ld1
#20 = Fieldref
#1.#21
//
com/sunld/TestString2.str1:Ljava/lang/String;
#21 = NameAndType
#7:#6
//
str1:Ljava/lang/String;
#22 = Utf8
LineNumberTable
#23 = Utf8
LocalVariableTable
#24 = Utf8
#25 = Methodref
#3.#26
//
V
#26 = NameAndType
#24:#16
//
"":()V
#27 = String
#28
//
sunld
#28 = Utf8
sunld
#29 = Fieldref
#1.#30
//
com/sunld/TestString2.str:Ljava/lang/String;
#30 = NameAndType
#5:#6
//
str:Ljava/lang/String;
#31 = Fieldref
#1.#32
//
com/sunld/TestString2.str3:Ljava/lang/String;
#32 = NameAndType
#12:#6
//
str3:Ljava/lang/String;
#33 = Utf8
this
#34 = Utf8
Lcom/sunld/TestString2;
#35 = Utf8
SourceFile
#36 = Utf8
TestString2.java
在代码中分别声明字符串str:实例变量、类变量、类常量、实例常量;通过字节码解析发现字符串字面量直接拼接编译期会自动组合成一个放入到字符串常量池。实例变量、类变量的属性索引是在init、cinit(运行时)方法中指向字面量;类常量和实例常量是直接指向字面量(编译时)。