-
浅析Java常量池
java中有几种不同的常量池,以下的内容是对java中几种常量池的介绍,其中最常见的就是字符串常量池。
(1)class常量池
在Java中,Java类被编译后就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用,每个class文件都有一个class常量池。
其中字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。
(2)运行时常量池
运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,是方法区的一部分(JDK1.8 运行时常量池在元空间,元空间也是方法区的一种实现)。不同之处是:它的字面量可以动态的添加(String类的intern()),符号引用可以被解析为直接引用。
JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和字符串类型(即通过String.intern()方法可以强制将String放入常量池),运行时常量池是每个类私有的。在解析阶段,会把符号引用替换为直接引用。
(3)基本类型包装类常量池
Java 基本类型的包装类的大部分都实现了常量池技术。Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True或False,如果超出对应范围就会去创建新的对象。两种浮点数类型的包装类Float,Double并没有实现常量池技术。
Integer 缓存源码:
/**
*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
}
举个栗子:
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));
结果:
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
解释:1-4语句结果应该很显然,因为Integer i1=40
这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40)
,Integer.valueOf()
方法基于减少对象创建次数和节省内存的考虑,缓存了[-128,127]之间的数字,如果在此数字范围内直接返回缓存中的对象。在此之外,直接new出来,显然40在常量池的缓存[-128,127]范围内;因此,i1
直接使用的是常量池中的对象。而Integer i1 = new Integer(40)
会直接创建新的对象;语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较,所以结果为true。第六条语句同理。
额外说明:所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
对于Integer var = ?在-128至127之间的赋值,Integer对象是在 IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,推荐使用equals方法进行判断。
(4)字符串常量池
在JDK1.6及之前版本,字符串常量池存放在方法区中的,在JDK1.7版本以后,字符串常量池被移到了堆中了。
HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet<String>;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。
注意:它只存储对java.lang.String实例的引用,而不存储String对象的内容
字符串常量池和上面的基本类型包装类常量池有些不同,字符串常量池中没有事先缓存一些数据,而是如果要创建的字符串在常量池内存在就返回对象的引用,如果不存在就创建一个放在常量池中;
在Java中,有两种创建字符串对象的方法,一种是字面量直接创建,另一种是new一个String对象,这两种方法创建字符串对象的过程会不一样;
(1)String str = "abc";
(2)String str = new String("abc");
如果是第一种方式创建对象,因为是字面量直接创建,所以在编译的时候是确定的,如果该字符串不在常量池中会将该字符串放入常量池中并返回字符串对象的引用,如果在常量池中直接返回字符串对象的引用,如果是第二种方式创建对象,因为要创建String类型的对象,String对象是在运行时才加载到内存的堆中的,属于运行时创建,所以要先在堆中创建一个String对象,再去常量池中寻找是否有相同的字符串,如果有就返回堆中Sring对象的引用,如果没有则在将该字符串加入常量池中。
举个栗子:
比较下列两种创建字符串的方法:
String str1 = new String("abc");
String str2 = "abc";
答案:第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 运行时期创建 。
第二种是先在栈中创建一个对String类的对象引用变量str2,然后通过符号引用去字符串常量池里找有没有”abc”,如果没有,则将”abc”存放进字符串常量池,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。“abc”存于常量池在 编译期间完成 。
String s = new String("abc")
这条语句创建了几个对象?
答案:共2个。第一个对象是”abc”字符串存储在常量池中,第二个对象在Java Heap中的 String 对象。这里不要混淆了s是放在栈里面的指向了Heap堆中的String对象。
String s1 = new String("s1") ;
String s1 = new String("s1") ;
上面一共创建了几个对象?
答案:3个 ,编译期常量池中创建1个,运行期堆中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)
出处:https://www.cnblogs.com/pluto-blog/p/15244795.html