Java常量池
JAVA的常量池有多个种类,主要有Class文件常量池、字符串常量池、基本数据类型常量池以及运行时初常量池。
Class文件是JAVA编译生成的JVM识别的字节码文件,是二进制文件。文件的定义遵守一定的格式。Class文件种存在常量池,主要包括字面量和符号引用。
字面量主要是只文本字符串的数值以及final修饰的变量;这里所说的都是数值是存在常量池中。
符号引用主要是指:
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
Class文件常量池自从JDK8开始,就被放到了元空间(MetaSpace)中,这是一种堆外内存,直接存储到操作系统内存中。
看下面的一个例子:
public class Changliang {
public static void main(String[] args) {
System.out.println(Demo.str);
}
}
class Demo {
public static final String str = "Hello,world";
static {
System.out.println("static execute");
}
}
输出:
Hello,world
上面并没有执行Demo的static,因为没有对Demo进行初始化,调用的str是存储于常量池中的变量。
运行时常量池是JVM完成类的加载操作后,会将class文件常量池加载到内存中。在这个过程会生成类对象,类对象就是方法区各个方法访问的入口。
运行时常量池的作用是存储java class文件常量池中的符号信息,运行时常量池中保存着一些class文件中描述的符号引用,同时在类的解析阶段还会将这些符号引用翻译出直接引用(直接指向实例对象的指针,内存地址),翻译出来的直接引用也是存储在运行时常量池中。
运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在运行时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()。
全局字符串常量池
该种常量池存储在堆中(JDK7开始),主要存储字符串的引用。它里面主要是维护了一张引用表。比如字符串的intern方法,就会首先去这张引用表中找到又没有对应的引用,如果有就直接使用,如果没有再新创建对象。
比如我们String s= "a";
字面量"a"是在编译存储在Class文件常量池中,而在Class文件常量池装载到运行时常量池的过程中,还会同时在堆上创建一个对象,全局字符串常量池会存储对象的引用。
再说下对于String s = new String("aa");这种是每次都会在堆内存新创建一个对象。
基本类型的封装类对应常量池。
比如 Boolean, Integer,Long(不包括double和float)等等,但整数也只是 -127~127之间的。
例子先上:
@Test
public void testInteger() {
Integer a = 100;
Integer g = new Integer(0);
Integer b = 100;
System.out.println(a == b); //true
Integer c = 200;
Integer d = 200;
System.out.println(c == d); //false
Float e = (float) 1.2;
Float f = (float) 1.2;
System.out.println(e == f); //false
String st1 = "dddd";
String str2 = new String("dddd");
System.out.println(st1 == str2); //false
String st3 = str2.intern();
System.out.println(st1 == st3); //true
System.out.println( b == a + g); //true
}
整数常量对于 -128-127的整数有缓存,因此在这个范围内的不会新创建对象。float和double不存在常量池。
上面其实涉及到Java的自动装箱和拆箱,也就是几大基本数据类型和对应封装的类之间的相互转化。
最后,主要注意以下最后一个语句, b == a +g,这是因为 运算符“+”不适用于Integer对象,因此Java首先进行拆箱操作,将Integer类拆分成int类型,然后进行数值相加,此时变成了 b == 100,接着,Integer类无法和数值直接比较,就会将b这个对象进行拆箱操作,将其变成数值,最后左右两个数值相比较,相等,返回true.
关于字符串,再看一个例子:
String c1 = "ad";
String d1 = "c";
String c2 = "ad" + "c";
String c3 = "adc";
String c4 = c1 + d1;
System.out.println(c2 == c3); //true
System.out.println(c4 == c3); //false
注意最后两个的不同,对象之间进行拼接操作,并不会加入到常量池中。
参考资料:
微信分享/微信扫码阅读