常量池存在于方法区(元空间)中,存放jvm运行期间的常量信息,常量大致由两种常量组成,字面量或者符号引用
常量池
字面量
字面量由数字,字母,字符组成,其实字面量就是通常赋值给基本数据类型或者封装类的值
String a = "abc";
//字面量:abc
String a = "a123";
//字面量:a123
int a = 10;
//字面量:10
char a = 'b';
//字面量: b
符号引用
简而言之,符号引用就是你编程时看到的一切的字符.例如类名,方法名,变量名,修饰符,括号,引号,冒号,分号.通过命令行javap命令我们可以打印jvm可视化的指令.
第一行:#1 = Methodref(方法映射)指向了#8和#31,首先让我们找到#8这一行#8=Class(类),这一行只想了#40,接下来让我们找到#40这一行,#40 = utf8(编码格式),这一行就存在这我们的常量,也就是符号引用 java/lang/Object.
接下让我们梳理一下,通过第一行的第二个引用加深印象,我们通过#31找到这一行,信息为#31 = NameAndType(名称和类型),这一行又指向了#9:#10,通过这两信息我们可以找到两个符号引用常量,组合起来就是<init>:()V.
init这个方法是我们对象创建的初始化方法,在创建对象调用我们的构造方法时执行,()V代表此方法不需要返回值,,换言之它就是返回值void.
通过这一系列操作,最终就会拼接成#1中注释的内容 java/lang/Object."<init>":()V 初始化object,返回值void.这一系列将符号引用替换成直接引用的过程,称作动态链接.
此时我们可以看到,常量池由两部分组成,分别是字面量和符号引用.
字符串常量池
接下来我们看一下一种常量池.字符串常量池.
----String str = "abc";创建过程
首先在创建这个对象的时候jvm会看一下在字符串常量池中是否有"abc"这个常量,如果没有就创建一个,如果有,直接将对象指向这一常量;
刚刚我们创建str这个对象,大致流程为:因为字符串常量池中没有"abc"这一常量,所以第一步是在常量池中创建"abc",第二步,将str这一句柄指向常量池中的"abc".
接下来我们在刚刚的基础上在创建一个对象String str2 = "abc"; 创建流程与上面相同,首先第一步查看字符串常量池中是否存在"abc",存在,则直接将str2指向"abc".
下面这个例子来说明两者的关系
String str = "abc";
String str2 = "abc";
System.out.println(str==str2);//true
我们都知道,在比较字符串的值时候,我们使用的是equals()方法,因为==操作是判断对象的hashcode值是否相等.equals()方法属于超类Object,默认实现就是==操作.而在String类中这个方法被重写.重写规则为,先判断对象是否相等,是则返回true,如果不是则将两个字符串转化为char[]数组,然后循环嵌套判断是否字符串的值是否相等,如果是返回true,如果不是返回false. 然而在这个例子中,我们在两个对象中使用==操作依然返回了true,这就是因为str与str2都指向了同一个对象.
接下来让我们在看一个例子加深印象
String str = "abc";
String str2 = new String("abc");
System.out.println(str==str2);//false
为什么在这个例子中,两个对象的==操作结果返回了false呢.那是因为第二个对象与第一个对象的创建流程稍有不同.
在创建str2这个对象的时候,首先查看字符串常量池中是否存在"abc",由于在创建str的时候已经在字符串常量池中创建了这个对象,所以接下来通过new操作,在jvm的堆中创建了一个String()对象,并且将此对象引用指向的字符串常量池中的"abc", 所以在str==str2中,我们比较的是字符串常量池中的"abc"对象与堆中的String()对象,结果自然不同.
然后在看这个例子;
String str = new String("abc");
String str2 = new String("abc");
System.out.println(str==str2);//false
那么此时结果就显而易见了,false,因为此时==操作针对的是堆中的两个不同对象
具体引用关系如下图
基本数据类型封装类常量池
其实在基本数据类型的封装类也实现了常量池的概念.请看一下代码
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);//true
Integer i3 = new Integer(300);
Integer i4 = new Integer(300);
System.out.println(i4 == i3);//false
代码的具体逻辑与上述相同
当然基本数据类型封装类常量池的常量是有大小上限的,比如下例
Integer i5 = 300;
Integer i6 = 300;
System.out.println(i5 == i6);//false
这里输出false就是因为Integer的值超出范围(-128~127).
以上就是常量池的基本概念与一些应用举例