Java中的几种常量池

参考https://www.zhihu.com/question/55994121

1.运行时常量池:方法区的一部分,存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储到运行时常量池中。运行时常量池具备动态性,也就是并非预置入Class文件的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中。

2.字符串常量池:本质是一个HashSet<String>,这是一个纯运行时的结构,而且是惰性维护的。注意它只存储String对象的引用,而不存储String对象的内容,根据这个引用可以得到具体的String对象。

3.Class常量池:主要存放两大类常量:字面量和符号引用。加载Class文件时,Class文件中String对象会进入字符串常量池(这里的进入是指 放入字符串的引用,字符串本身还是在堆中),别的大都会进入运行时常量池。

字面量比较接近Java语言层面常量的概念,如文本字符串、声明为final的常量值

符号引用属于编译原理的概念:

    类和接口的全定限名

    字段的名称和描述符

    方法的名称和描述符

符号引用将在解析阶段被替换为直接引用。因为Java代码在进行编译时,并不像C那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,Class文件不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期间 转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

4.String的intern方法:

JDK7中,如果字符串常量池中已经有了这个字符串,那么直接返回常量池中的它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用

5.字面量进入字符串常量池的时机

就HotSpot VM的实现来说,加载类的时候,那些字符串字面 量会进入当前类的运行时常量池,不会进入全局字符串常量池(即在字符串常量池中没有相应的引用,在堆中也没有生成对应的对象)。加载类的时,没有解析字符串字面量,等到执行ldc指令的时候就会触发这个解析的动作ldc指令的语义是:到当前类的运行时常量池区查找该index对应的项,如果该项没有解析就解析,并返回解析后的内容。在遇到String类型常量时,解析的过程是如果发现字符串常量池中已经有了内容匹配的String类型的引用,就直接返回这个引用,如果没有内容匹配的String实例的引用,就会在Java堆中创建一个对应内容的String对象,然后在字符串常量池中记录下这个引用

说明:自己的一点理解,上面说的时对字符串的解析,其实对方法解析也是类似,有些方法也是lazy resolve,有一部分符号引用是在类加载阶段或者第一次使用的时候就转化为直接引用,被称为静态解析(例如静态方法、私有方法等非虚方法),另一部分将在每一次运行期间转换为直接引用,被称为动态连接(例如静态分派),这部分也是lazy resolve。

6.例题分析:

例1:

class Test{
	public static String s1 = "static";
	public static void main(String[] args) {
		String s2 = new String("he")+new String("llo");
		s1.intern();
		String s3 = "hello";
		System.out.println(s1==s2);  //true
	}
}

"static" "he" "llo" "hello"都会进入Class常量池,类加载阶段由于解析阶段时lazy的,所以不会创建实例,更不会驻留字符串常量池。但要注意这个“static"和其他三个不一样,它是静态的,在加载阶段的初始化阶段,会为静态遍历执行初始值,也就是将"static"赋值给s1,所以会创建"static"字符串对象, 并且会保存一个指向它的引用到字符串常量池。

运行main方法后,执行String s2 = new String("he")+new String("llo")语句,创建"he"和"llo"的对象,并会保存引用到字符串常量池中,然后内部创建一个StringBuilder对象,一路append,最后调用toString()方法得到一个String对象(内容时hello,注意这个toString方法会new一个String对象),并把它赋值给s2(注意这里没有把hello的引用放入字符串常量池)。

然后执行语句:s1.intern(),此时字符串常量池中没有,它会将上面的这个hello对象的引用保存到字符串常量池,然后返回这个引用,但是这个返回的引用没有变量区接收,所以没用。

然后执行:String s3 = "hello"因为字符串常量池中已经有了,所以直接指向堆中"hello"对象

然后执行:System.out.println(s1==s2),此时返回true。

示意图如下:


例题2:

class JianZhiOffer{
	public static void main(String[] args) {
		String s1 = new String("he")+new String("llo");    //第一句
		String s2 = new String("h")+new String("ello");    //第二句
		String s3 = s1.intern();                           //第三句
		String s4 = s2.intern();                           //第四句
		System.out.println(s1==s3);                        //第五句
		System.out.println(s1==s4);                        //第六句
	}
}

类加载阶段,什么都没干。

第一句:创建"he"和"llo"对象,并放入字符串常量池,然后创建"hello"对象,没有放入字符串常量池,s2指向这个"hello"对象。

第二句:创建了"h"和"ello"对象,并放入字符串常量池,然后创建"hello"对象,没有放入字符串常量池,s3指向这个"hello"对象。

第三句:字符串常量池中没有"hello",所以会把s1指向String对象的引用放入字符串常量池,然后将这个引用返回给了s3,所以s1==s3是true

第四句:字符串常量池中有了"hello",所以将s4指向的s3指向的对象"hello",所以第六句s4==s1是true。

相关文献参考:

知乎:

https://www.zhihu.com/question/55994121

https://www.zhihu.com/question/29884421/answer/113785601

https://www.zhihu.com/question/55328596

美团文章:

https://tech.meituan.com/in_depth_understanding_string_intern.html


猜你喜欢

转载自blog.csdn.net/chenkaibsw/article/details/80848069