class常量池

java文件被编译成 class文件,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池(Constant Pool),用于存放编译器生成的各种字面量( Literal )和 符号引用(Symbolic References)

  • 字面量

( Literal )就是我们所说的常量概念,如文本字符串、被声明为final的常量值等

  • 符号引用(Symbolic References)

一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
一般包括下面三类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符

字符串常量池

是在类加载完成,经过验证准备阶段之后 在 中生成字符串对象实例,然后 将该字符串对象实例的 引用值 存到 String Pool 中。

字符串常量池在 Java 内存区域的哪个位置

  • 在 JDK6.0 及之前版本,字符串常量池是放在 Perm Gen 区(也就是方法区)中,此时常量池中存储的是对象。
  • 在 JDK7.0 版本,字符串常量池被移到了堆中了。此时常量池存储的就是引用了。在 JDK8.0 中,永久代(方法区)被元空间取代了。

创建字符串的两种方式

字面量赋值

JVM首先会去字符串池中查找是否存在”aaa”这个对象,如果不存在,则在字符串池中创建”aaa”这个对象,然后将池中”aaa”这个对象的引用地址返回给字符串常量str,这样str会指向池中”aaa”这个字符串对象;
如果存在,则不创建任何对象,直接将池中”aaa”这个对象的地址返回,赋给字符串常量.
所以下例中s1 和 s2 指向同一个字符串。

这个过程是在编译时完成的

  1. String s1 = "Hello";
  2. String s2 = "Hello";
  3. s1 == s2 // true

new创建对象

new关键字创建string对象,每次都会在堆中创建新的对象。

这个过程实在运行时完成的

  1. String s3 = new String("Hello");
  2. String s4 = new String("Hello");
  3. s3 == s4 // false

字符串拼接运算

如果只涉及到字面量的拼接,那么拼接过程在Java编译器编译期间就完成了,并且结果会被放在字符串池。
如果涉及到字符串对象的拼接,那么就会在程序执行期间才会进行计算,它会在堆内存中重新创建一个拼接后的字符串对象。

  1. String s5 = "Hello";
  2. String s7 = "Hel";
  3. String s8 = "lo";
  4. String s9 = s7 + s8;
  5. s5 == "Hel" + "lo"; // true,编译期完成,直接将拼接结果放入常量池(已存在就不放了),避免创建多个字面量
  6. s5 == s7 + s8 // false 运行时完成,产生新对象
  7. 值得注意的是
  8. s9 == s7+s8 // 也是false,也产生了新的对象

总结:

  • 字面量”+”拼接是在编译期间进行的,拼接后的字符串存放在字符串池中;
  • 字符串引用的”+”拼接运算实在运行时进行的,新创建的字符串存放在堆中。

    运行时常量池

    class常量池是class字节码文件中的一部分,java文件被编译成class字节码后就产生了class常量池。
    而运行时常量池是方法区的一部分。
    运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个 class 都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致

    class常量池是java文件编译产生的,是静态的。 而运行时常量池不仅仅包含从class常量池转存的内容,还具备动态性,可以动态添加内容值运行时常量池。 如运行期间通过 intern 方法,将字符串常量存入到字符串常量池中和运行时常量池

JVM中的常量池 - 图1