一、字符串常量池(String Pool)——位于方法区
1.结构:
它是一个String Table类,实质上是一个Hash表,默认长度是1009。全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例存到string pool中。(关于字符串常量池中存的到底是对象实例还是对象实例的引用,网上有各种说法,大家可以自行百度一下。)
2.特点:
A、这个String Table在每个JVM中的实例只有一份,被所有的类共享。
B、string pool中存的是具体的实例对象
3.存储的数据:
里面存的是驻留字符串(也就是我们常说的用双引号括起来的)实例(驻留字符串实例本身)。
二、class文件中的常量池——位于本地
1.结构:包含各种.class文件。也就是类编译后产生的文件
2..class文件
class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
字面量(字面量就是我们所说的常量概念):A、文本字符串;B、八种基本类型的值;C、被申明为final的常量等
符号引用:是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可(它与直接引用区分一下,直接引用一般是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。一般包括下面三类常量:
A、类和方法的全限定名;B、字段的名称和描述符;C、方法的名称和描述符
常量池的每一项常量都是一个表,一共有如下表所示的11种各不相同的表结构数据,这每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。
三、运行时常量池——位于方法区中
1.存储位置:内存
2.存储的数据:class常量池被加载到内存之后的版本。(它同样拥有所有的类)。不同之处是:它的字面量可以动态地添加(String#intern()),符号引用会被解析为直接引用。
符号引用和直接引用:比如String name="Java";假定它在String Pool中的hashcode=1。
如果文件中储存这个字符串时,存的是name。那么这个就是符号引用。
如果文件中存储这个字符串时,存的是1。那么这个就是直接引用。
显然所有的符号引用最终都要转化为直接引用才能够找到相应的数据。
3.加载过程
运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。
4.特点:对于String类型,可以在运行时通过intern方法将字符串加入运行时常量池。
四、重新来看new操作
我们以String s1=new String("abc");为例
首先当我们的类Class在被ClassLoader加载时,"abc"被作为常量读入,在String Pool(字符串常量池)创建了一个"abc"的实例。
然后,调用到new String("abc")的时候,会在Heap里面复制一个相同的对象。
考虑类加载阶段和实际执行时。
(1)类加载对一个类只会进行一次。"abc"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"abc"字符串被驻留过则不需要重复创建用于驻留的"abc"实例)。驻留的字符串引用是放在全局共享的字符串常量池中的。
(2)在这段代码后续被运行的时候,"abc"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象实例复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
因此,这条语句创建了2个对象。
五、测试样例
代码
public class TestString {
public static void main(String[] args) {
String name=new String("abc");
String name1="abc";
System.out.println(name==name1);
}
}
运行结果:
分析:
String name=new String("abc");这条语句在字符串常量池和Heap堆分别建立了一个值为"abc"的对象,并且把Heap中的这个对象交给name持有。
String name1="abc";这条语句首先会判断字符串常量池中有没有值为"abc" 的对象,发现有,于是就把字符串常量池中的这个对象交给name1持有。
因此这两个引用,它们的对象实例是不同的。
六、总结
1.字符串常量池(全局变量池)在每个JVM中只有一份,存放的是字符串常量的引用值
2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用
3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与字符串常量池中的引用值保持一致。
本篇博文的主要内容来自https://blog.csdn.net/qq_26222859/article/details/73135660
个人对其做了一些修正和整合。