1

前言

方法区(Method Area)是线程共享的一块内存区域,JVM加载的类型信息常量静态变量即时编译器编译后的代码缓存等数据均存放于方法区。

运行时常量池(Runtime Constant Pool)是方法区的一部分,在Class文件中有一部分内容为常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容在Class文件被加载到JVM后会被存放在运行时常量池中。

字符串常量池中存放字符串字面量,在JDK1.8中,字符串常量池存在于堆中。

本篇文章将对JDK1.8中的方法区,运行时常量池和字符串常量池的区域分布进行说明,并着重对字符串常量池进行分析以探究new一个字符串对象时到底会在堆上创建几个对象。

JDK版本:1.8
参考资料:《深入理解Java虚拟机第三版》

正文

一. 方法区的区域分布

方法区只是一个逻辑概念,在JDK1.8中方法区的具体实现为元空间,而元空间使用的是本地内存。在JDK1.8中,方法区,运行时常量池和字符串常量池的区域分布示意图如下所示。

即字符串常量池存在于堆中,并且如果要设置方法区大小,需要使用-XX:MaxMetaspaceSize=指令进行设置。

JDK1.8中使用元空间作为方法区的实现以替代永久代(PermGen),有如下原因。

  • 永久代作为方法区的实现时,字符串常量池存在于运行时常量池中,即字符串常量池存在于方法区中,而方法区只有Full GC时才会被清理,因此容易出现由于字符串常量池导致的内存溢出;
  • 类型信息等数据大小不容易确定,将其存放到本地内存更为合适。

二. 字符串常量池

首先回答那个经典的问题:new一个字符串对象会创建几个对象。答案是一个或者两个

字符串常量池中会存储字符串字面量,字符串字面量本质就是对象,当在代码中出现如下代码。

String str = "sakura";

如果字符串常量池中已经存在sakura这个字符串字面量,那么str会指向字符串常量池中的sakura字符串字面量,反之,则会先将sakura这个字符串字面量添加到字符串常量池中,然后再将str指向字符串常量池中的sakura字符串字面量。

更甚一步,其实只要代码中出现双引号括起来的字符串,那么就会去字符串常量池中寻找对应的字符串字面量,如果寻找不到,则创建字符串字面量并添加到字符串常量池中。

现在如果在代码中出现如下代码。

String str = new String("sakura");

首先出现了双引号括起来的sakura字符串,所以就会去字符串常量池中寻找对应的字符串字面量,如果寻找不到,则创建sakura字符串字面量并添加到字符串常量池中,如果寻找到,则直接使用字符串常量池中的sakura字符串字面量。最后,会在堆上创建一个字符串对象,str会指向堆上创建出来的字符串对象。所以new一个字符串对象时,可以肯定的是一定会在堆上创建一个字符串对象,但是字符串常量池中是否会创建一个字符串字面量,要取决于字符串字面量之前是否已经存在,已经存在则不会再重复创建。所以new一个字符串对象会创建几个对象的答案是一个或者两个。

为了加深理解,考虑如下的示例。

public class StringTest {

    public static void main(String[] args) {
        String str1 = new String("sakura") + new String("sakura");  // 步骤1

        String str2 = "sakurasakura";  // 步骤2

        System.out.println(str1 == str2);  // 步骤3
    }

}

当执行完步骤1后,堆上的情况如下所示。

执行完步骤2后,堆上的情况如下所示。

所以最终步骤3的打印结果一定是false

3. String的intern()方法

首先考虑如下的示例。

public class StringTest {

    public static void main(String[] args) {
        String str1 = new String("sakura") + new String("sakura");  // 步骤1
        
        str1.intern();  // 步骤2

        String str2 = "sakurasakura";  // 步骤3

        System.out.println(str1 == str2);  // 步骤4
    }

}

上述示例和第2小节中的示例差不多,只不过多了一步str1.intern()Stringintern()方法会根据当前字符串对象的值去字符串常量池中进行匹配,如果字符串常量池中存在字符串字面量的值与当前字符串对象的值相等,则返回这个字符串字面量的地址,如果字符串常量池中不存在字符串字面量的值与当前字符串对象的值相等,则在字符串常量池中注册一个引用并指向当前字符串对象,并最后返回当前字符串对象的地址。那么上述示例中,执行完步骤2后,堆上的情况如下所示。

执行完步骤3后,堆上的情况如下所示。

所以最终步骤4的打印结果一定是true

通过上述示例可以知道,字符串常量池中除了存储字符串字面量以外,还会存储指向堆上的字符串对象的引用。

总结

方法区用于存放JVM加载的类型信息常量静态变量即时编译器编译后的代码缓存等数据,JDK1.8中使用元空间作为方法区的实现,元空间使用的是本地内存。字符串常量池中会存储字符串字面量,字符串字面量本质就是对象,当new一个字符串对象时,一定会在堆上创建一个字符串对象,但是字符串常量池中是否会创建一个字符串字面量,要取决于字符串字面量之前是否已经存在,已经存在则不会再重复创建,所以new一个字符串对象会创建几个对象的答案是一个或者两个。


半夏之沫
65 声望32 粉丝