乐趣区

关于虚拟机:JAVA虚拟机方法区与字符串常量池

前言

办法区(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 一个字符串对象会创立几个对象的答案是一个或者两个。

退出移动版