常量池与方法区以及又读new String对象创建问题
又拿出这道String str1 = new String("abc");创建几个对象的面试题梳理了一下常量池与方法区的关系,希望能把这两者的关系通过这道面试题说明白
方法区是什么
简单说方法区用于存储jvm加载的类的信息、常量、静态变量、编译后的代码
方法区、永久代与元空间的关系
下文都以HotSpot来说明
首先方法区是JVM规范的说法,永久代、元空间是HotSpot用来实现方法区的两个具体的实现
JDK1.8以前使用永久代Perm实现了JVM规范中的方法区
JDK1.8废弃永久代,变更为元空间,不是废弃了方法区
永久代与元空间的区别是元空间不在虚拟机内存中,而使用本地内存,目的是为了融合HotSpot与JRockit VM而做出的努力并且减少,并且由于这部分空间的GC效果难以令人满意
上面我们就说清楚了方法区、永久代与元空间这几个名词之间的关系和区别,下面我们在来看三个常量池的关系和不同
常量池
字符串常量池
是一个哈希表(StringTable),里面存的是驻留字符串的引用
字符串驻留:JVM 为了提高性能会将能在编译时期确定的字符串放在字符串驻留池的内存块中,String a = "abc"; String b = new String("def");都在编译时期能确定主流字符串"abc""def"
在堆中的字符串实例被这个哈希表引用之后就等同被赋予了”驻留字符串”的身份
在JVM中字符串常量池被所有类共享
字符串常量池在JDK1.7的版本从永久代移动到了堆
class文件常量池
class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。注意这是class文件中的内容
字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等
符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,通常包含:类的全限定名、字段的名称和描述符、方法的名称和描述符
总之这些内容能表示这个类的代码内容
运行时常量池
JDK1.7仅仅把字符串常量池移动到了堆,JDK1.8虽然废弃了永久代变更为元空间,但是运行时常量池仍然跟随元空间被移出JVM内存
当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个
并且在类加载的解析阶段会把运行时常量池的符号引用替换成直接引用,这个过程需要查找字符串常量池
由上面的内容我们可知new String对象创建问题只和堆以及堆内的字符串常量池有关系(字符串常量池在不在堆对new String没什么影响)
1 //在堆中会有一个”abc”实例,全局StringTable中存放着”abc”的一个引用值 2 String str1 = "abc"; 3 //生成两个实例,一个是”def”的实例对象,并且StringTable中存储一个”def”的引用值,还有一个是new出来的一个”def”的实例对象(指向str2) 4 String str2 = new String("def"); 5 //查找StringTable,里面有”abc”的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同 6 String str3 = "abc"; 7 //调用intern()函数,返回StringTable中”def”的引用值,如果没有就将str2的引用值添加进去 8 String str4 = str2.intern(); 9 //最后str5在解析的时候就也是指向存在于StringTable中的”def”的引用值 10 String str5 = "def"; 11 //生成一个实例,new出来的一个”def”的实例对象(指向str6),(def母本与StringTable放入”def”引用值已经创建) 12 String str6 = new String("def"); 13 14 System.out.println(str1 == str3);//true 15 16 System.out.println(str2 == str4);//false 17 System.out.println(str4 == str5);//true 18 19 System.out.println(str5 == str2);//false 20 System.out.println(str6 == str2);//false 21 22 System.out.println(str6 == str2.intern());//false 23 System.out.println(str6.intern() == str2.intern());//true