常量池

340 阅读5分钟

常量池存在于方法区(元空间)中,存放jvm运行期间的常量信息,常量大致由两种常量组成,字面量或者符号引用

常量池

字面量

字面量由数字,字母,字符组成,其实字面量就是通常赋值给基本数据类型或者封装类的值

String a = "abc";
//字面量:abc

String a = "a123";
//字面量:a123

int a = 10;
//字面量:10

char a = 'b';
//字面量: b

符号引用

简而言之,符号引用就是你编程时看到的一切的字符.例如类名,方法名,变量名,修饰符,括号,引号,冒号,分号.通过命令行javap命令我们可以打印jvm可视化的指令.

可以看到此时打印出的信息Constant pool(常量池),这些就是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).

以上就是常量池的基本概念与一些应用举例