JVM-深入学习字符串常量池
第一二张图应该画错了,元空间的时候,字符串常量池已经移入堆内
首先声明,在JDK1.7
的时候,字符串常量池已经从方法区迁移到了堆内存,JDK1.8
的时候方法区改朝换代为元空间,同时也不在占用JVM
内存,而是使用本地内存
为什么多设计一个常量池,不能像其它对象一样乖乖待在堆中吗?鄙人大胆猜测:
- 对象的分配需要时间和空间的开销,一般在程序中。字符串使用的频段还是很高的,另一方面,常用的数据库密码、用户名啥的都是使用字符串保存的,如果不将其放入字符串常量池的话,使其频繁创建销毁的话,性能和安全性都是划不来的,所以这就是为什么会有字符串常量池和String为什么是final修饰的原因。
上代码,下面这种字面量声明的方式是我们最常用的方式。这样声明会直接将字符串”晓果冻“放入字符串常量池
再声明一个对象s2
,因为“晓果冻”已经在字符串常量池中存在了,所以s2
直接指向字符串常量池中的地址
String s2 = "晓果冻";
那new String("")
呢,分2种情况
- 被
new
的字符串已经在常量池存在,那么堆就会指向字符串常量池中的地址
为什么会是false
呢,因为s1引用的是字符串常量池中的地址,s2引用的是堆内地址,但堆内对象是引用了字符串地址的,如果 s2 = s2.intern();
其实这里也不能算第二种情况,就是new好了再调用上面的**intern()**方法,又是另一种情形;
intern():不管使用什么方式定义一个字符串,都会首先在常量池中查找是否有相应的字符串存在,如果有,直接返回引用,否则,在常量池中生成相应的字符串并返回引用;
下面上代码
代码语言:javascript复制package com.company;
public class Main {
public static void main(String[] args) {
// write your code here
String s1 = "晓果冻";
String s2 = "晓果冻";
String s3 = "晓" "果冻";
String s4 = new String("晓");
String s5 = new String("果冻");
String s6 = s4 "果冻";
String s7 = s4 s5;
String s8 = s6.intern();
String s9 = s7.intern();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s6);
System.out.println(s6 == s7);
System.out.println(s6 == s8);
System.out.println(s8 == s9);
System.out.printf(s7);
}
}
号操作是有内部优化处理操作的,其实也是又new了一个新的字符串对象,所以上图字符串常量池中的"晓"和"果冻"字符串没有被引用。因为这种 操作这种图我不知道该怎么画,所以只能指向最终的堆中地址。
代码语言:javascript复制s6.intern()如果不把返回值赋值给s6,那么栈内存中的对象s6还是引用堆中的地址。
只有s6=s6.intern();//这样栈内存中的对象s6才会引用常量池中的地址,故s1==s6
代码语言:javascript复制String s10 = new String("晓果冻");
/如果字符串常量池中没有"晓果冻"字符串的话,那么直接在堆中创建该字符串,并不会复制一份到字符串常量池的,大多数人都会以为会复制一份到字符串常量池,其实不然。
只有当s10.intern();
在常量池新增了一个对象,但是并没有将字符串复制一份到常量池,而是直接指向了之前已经存在于堆中的字符串对象。因为在 JDK 1.7
之后,字符串常量池不一定就是存字符串对象的,还有可能存储的是一个指向堆中地址的引用,