java常量池分为Class常量池、运行时常量池和字符串常量池
Class常量池
class文件中除了类的版本、字段、方法、接口等信息等,还有class常量池,它用于存放编译阶段生成的各种字面量和符号引用。
字面量: 给基本类型变量赋值的字面量或者说是字面值,
如int i = 6;String str = “abc”,这里的6和“abc”都是字面量;
符号引用: Java程序在编译成class文件时,虚拟机并不知道类(接口)、类中引用对象、字段名称和描述符、方法名称和描述符的实际地址,就用符号引用来代替,在类装载器装载类时会通过符号引用得到真实地址(实际内存地址)。
字符串常量池
注:这个概念是有争议的
jdk1.8中字符串常量池是放在堆中的,需要了解透彻字符串String
String对象是对char[]数组封装实现的对象:
其中String类被final修饰,其中char数组也被private final修饰,这也导致了他的不可变性,这种不可变性给Java带来了以下几点好处:
- 保证了String的安全性;
- 保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能;
- 可以实现字符串常量池。在 Java 中,通常有两种创建字符串对象的方式,一种是通过字符串常量的方式创建,如 String str=“abc”;另一种是 字符串变量通过 new 形式的创建,如 String str = new String(“abc”)。
String的创建方式及内存分配的方式
- String str1 = “abc”;
当代码中是这种创建方式时,jvm会先去字符串常量池检查是否有“abc",如果有,就返回该对象引用,否则就在字符串常量池中区创建; - String str2 = new String(“abc”);
在编译期,字符常量"abc"会被放在常量结构中,在类加载时,“abc"会在常量池中创建,在调用new时,jvm命令会调用String构造函数,同时在堆中创建一个String对象引用常量池中的"abc”,最后栈中的str2引用这个String对象。
其中将常量池中的"abc"引用到堆中具体是先将常量池中的对象压入栈中,在使用String方法的构造器时,去拿到栈中的字符串作为构造方法的参数,这个构造函数是一个char[]数组的赋值过程,而不是new 出来的,所以是引用了常量池中的对象。 - String str3 = “ab”+“cd”+“ef”;
编译器会自动优化为String str3 = “abcdef”,而不会一个个去创建对象;
String str4 = “gh”;String str5 = “abc”+str4;这里不会优化; - 循环中使用+
其中在进行字符串拼接时,编译器会自动优化,偏向使用StringBuilder
intern
String的intern方法,如果常量池中有这个字符串就会返回这个字符串的引用。
如果需要查看 String 的编译优化,需要使用到反编译工具,推荐 JD-GUI:http://java-decompiler.github.io/