理解Java常量池
Posted 黄智霖-blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解Java常量池相关的知识,希望对你有一定的参考价值。
注:本文的论述和代码测试均站在HotSpot的角度
前言
在java虚拟机规范中,字符串常量池存储在方法区,而方法区是规范中的一个概念模型,没有限制虚拟机如何实现。在JDK1.6之前,字符串常量池存储于永久代中,而在1.7之后转移到了堆中,并且方法区在1.8中已经使用元空间来实现,放弃了永久代实现。
概述
Java中字符串常量的产生有两种方法:
- 直接使用双引号""声明的字符串,会直接进入常量池
- 使用String类提供的intern方法,会从字符串常量池中查询当前字符串是否存在,如果不存在就会将其存入常量池
验证
注:这里暂时只分析1.7之后的情况;假设代码示例中使用""引用的字符串常量在之前都不存在于字符串常量池中;假设涉及到的对象都在堆上分配
- 场景1:
String str1 = new String("789");
String str2 = "789";
System.out.println(str1 == str2);
//输出false
分析:第一行代码使用了"789",其会被放到字符串常量池中;str1是通过new String创建的一个新对象,存在于堆中,创建的时候发现常量池中已经有一个"789"了,那么将str1对象的value指向常量池中对应对象的char数组;之后通过str2引用了"789",此时发现常量池中已经有对应的字符串串,直接返回其引用。
上面提到了,String的底层是通过char数组存储的,虽然str1和str2指向不同的对象,但是在创建str1的时候发现常量池中已经有对应的对象了,那么直接使用常量池中对象的char数组。
这里简单验证一下:
String str1 = new String("789654");
String str2 = "789654";
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
Object value1 = field.get(str1);
Object value2 = field.get(str2);
System.out.println(value1 == value2);
((char[]) value2)[2] = 'x';
System.out.println("str1:" + str1 + ",str2:" + str2);
//输出
true
str1:78x654,str2:78x654
发现str1和str2的value是同一个对象,修改了str2对象的value(常量池中)之后,str1对应的value也发生了变化。
- 场景2:
String str1 = new String("abc") + "12312";
String str2 = "abc12312";
System.out.println(str1 == str2);
//输出 false
分析:第一行代码中有两个字符串会进入字符串常量池,分别是"abc"和"12312",中途创建了一个对象new String(“abc”),这里的+号是通过StringBuilder实现的。str2引用了"abc12312",由于此时常量池中没有该字符串,所以会被存入常量池。str1指向堆中的对象,str2指向常量池中的对象。
- 场景3:
String str1 = new String("abc") + "12312";
str1.intern();
String str2 = "abc12312";
System.out.println(str1 == str2);
//输出 true
分析:和场景2的区别就是多了一个str1.intern()方法的调用。在第一行代码执行完成后,常量池中有"abc"和"12312",堆中有"abc12312"。通过调用str1.intern方法,发现常量池中不存在"abc12312",那么尝试将其存入常量池:常量池中的对象指向堆中的对象。而str2指向常量池中的对象,所以他们相同。
- 场景4
String str1 = "abc" + "12312";
String str2 = "abc12312";
System.out.println(str1 == str2);
//输出true
分析:“abc” + “12312"会被编译器优化为"abc12312”,所以str1和str2都指向常量池中的对象。
以上是关于理解Java常量池的主要内容,如果未能解决你的问题,请参考以下文章