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

注意最后两个的不同,对象之间进行拼接操作,并不会加入到常量池中。

参考资料:

--------EOF---------
微信分享/微信扫码阅读