两种常量池
一、静态常量池
- 要了解常量池,需要先对 jvm 的内存模型有一定的了解
1、jvm 内存模型
- 如上图所示,JVM 的内存区域基本分为五个部分
本地方法栈
- 调用操作系统方法所用的栈
程序计数器
- 就是指示字节码的执行行数的
虚拟机栈
- 就是执行 java 代码的栈
堆
- 一般用来存储对象,还包括常量池等
方法区
- 可以理解成class文件在内存中的存放位置,存放的就是已加载类的一些基本信息
2、静态常量池
- 所谓静态常量池就是class文件中的常量池,主要用于存放两大类常量:字面量和符号引用量
- 字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等
- 符号引用包括了如下三种类型的常量:类和接口的全限定名、字段名称和描述符、方法名称和描述符
二、动态常量池
- 运行时常量池是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在虚拟机堆中,一般说的常量池就是指的这个
- 运行时常量池最重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件中常量池的内容才能进入堆中的常量池,运行期间也可能将新的常量放入池中
- String的intern()方法:查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池
字符串常量池
- 基本来讲我们接触最多的就是字符串常量池
一、经典示例
1、示例代码
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
2、示例说明
- s1 == s2 很好理解
- s1 == s3 是编译器针对 + 操作符的优化,但是这样的优化仅限两边都是常量的时候
- s1 == s4 new的部分不可预知,编译器无法进行优化
- s1 == s9 s7和s8都是变量也是无法优化的
- s4 == s5 都会产生新的对象存放于堆中,肯定不同的
- s1 == s6 intern 方法就是先去常量池中找,这里显然是找到了
二、几种特例
- A和B明显就是两个常量而且是立即初始化的,所以这里编译器就可以进行优化了
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
String s = A + B; // 将两个常量用+连接对s进行初始化
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
//s等于t,它们是同一个对象
- 这里虽然也是常量但是没有立即初始化,存在不确定性,编译器无法进行优化,因为编译器也不是万能的呀
public static final String A; // 常量A
public static final String B; // 常量B
static {
A = "ab";
B = "cd";
}
public static void main(String[] args) {
// 将两个常量用+连接对s进行初始化
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
//s不等于t,它们不是同一个对象
总结
- 必须要关注编译期的行为,才能更好的理解常量池。
- 运行时常量池中的常量,基本来源于各个class文件中的常量池。
- 程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池
补充
- 整数类型包装类BYTE范围内(-128到127)是存放于常量池的
- 浮点类型的包装类没有实现常量池技术
参考文章:深入浅出java常量池