Java字符串常量池

一、开篇

同学们面试的时候总会被问到字符串常量池的问题吧?如果你是死记硬背的答案,那么我想看了我这篇文章,你应该以后能胸有成竹了。

跟着Alan,走起!


二、概述

1. 常量池表(constant_pool table)

  1. Class文件中存储所有常量(包括字符串)的table。
  2. 它是Class 字节码文件中的一类结构化数据,还不是运行时的内容

2. 运行时常量池(Runtime Constant Pool)

  1. JVM 运行时内存中方法区的一部分,这是运行时的内容。
  2. 这部分数据绝大部分是随着JVM 运行,从常量池表转化而来,每个Class 都对应一个运行时常量池。
  3. 上面说绝大部分是因为:除了Class 中常量池内容,还可能包括动态生成并加入这里的内容。

3. 字符串常量池(String Pool)

  1. 字符串常量池与运行时常量池不是一个概念:

    • String Pool 是JVM 实例全局共享的全局只有一个,而Runtime Constant Pool 每个类都有一个。
    • String Pool 只记录字符串对象,而Runtime Constant Pool 记录各种对象。
  2. JVM规范要求进入这里的String 实例叫“被驻留的字符串 - interned string”,各个JVM 可以有不同的实现,HotSpot 是设置了一个哈希表 - StringTable 来引用堆中的字符串实例,被引用就是被驻留。

  3. 字符串池在JDK 1.7 之后存在于Heap 堆中,旧版存在于方法区中。

4. 享元模式

其实字符串常量池这个问题涉及到一个设计模式,叫“享元模式”,顾名思义 - - - > 共享元素模式。

也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素。

Java中String部分就是根据享元模式设计的,而那个存储元素的地方就叫做“字符串常量池 - String Pool”。


为了描述准确,以下我将用上述的3个英文来描述:constant_pool tableRuntime Constant PoolString Pool


三、详解

感觉好像前面1和2说了点概念,就好像快说完了…… 那直接来个Example 吧!

*.java文件中有如下代码:

int a = 1;
String b = "asd";
  1. 首先,1"asd"会在经过javac(或者其他编译器)编译过后变为Class文件中constant_pool table的内容。

  2. 当我们的程序运行时,也就是说JVM运行时,每个Class字节码 constant_pool table中的内容会被加载到JVM 内存中的方法区中各自Class对象的Runtime Constant Pool中。

  3. 一个没有被String Pool包含的Runtime Constant Pool中的字符串(这里是"asd")会被加入到String Pool中(HosSpot 使用hashtable 引用方式),步骤如下:

    1. 在Java Heap 中根据"asd"字面量创建一个字符串对象。

    2. 将字面量"asd"与字符串对象的引用在hashtable 中关联起来,键 - 值 形式是:"asd" = 对象的引用地址

另外来说,当一个新的字符串出现在Runtime Constant Pool中时怎么判断需不需要在Java Heap中创建新对象呢?

  • 策略是这样:

    会先去根据equals来比较Runtime Constant Pool中的这个字符串是否和String Pool中某一个是相等的(也就是找是否已经存在),如果有那么就不创建,直接使用其引用;反之,如上3。

如此,就实现了享元模式,提高的内存利用效率。

四、例子

上面的描述其实对于能看到这篇文章的程序员们来说很好懂,下面我们来分析两个具体的问题。

  1. String s = new String(“abc”) 创建了几个对象?

    结论:2个或1个。

    解析:

    1. 首先,出现了字面量"abc",那么去String Pool中查找是否有相同字符串存在:如果存在就不会新建对象,否则就在Heap 中用字面量"abc"首先创建1个String 对象。

    2. 接着,new String("abc"),关键字new 一定会在Heap 中创建1个新对象,然后调用接收String 参数的构造器进行初始化。变量s的引用是这个String 对象。

  2. 说明以下程序的输出?

    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = "a" + "bc";
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s1 == s1.intern());
    }

    结论:

    System.out.println(s1 == s2);//flase
    System.out.println(s1 == s3);//true
    System.out.println(s1 == s1.intern());//true

    解析

    1. 首先,与题1所答同理,s1 和s2 肯定不是一个引用。

    2. 其次String s3 = "a" + "bc";这行代码最终是"abc",根据s1,它已经在String pool中了,所以s3和s1是引用的同一个对象。

    3. 最后,s1.intern()方法:将某个String对象在运行期动态的加入String pool(如果pool中已经有一个了就不加)并返回String pool中保证唯一的一个字符串对象的引用。所以,还是会返回和s1同一个对象的引用,所以true



参考文献:

[ 1 ] 周志明.深入理解Java虚拟机[M].第2版.北京:机械工业出版社,2015.8.
[ 2 ] Tim Lindholm,Frank Yellin,Gilad Bracha,Alex Buckley.The Java® Virtual Machine Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.
[ 3 ] James Gosling,Bill Joy,Guy Steele,Gilad Bracha,Alex Buckley.The Java® Language Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.



追加记录一个我追踪OpenJDK 关于String pool实现方式的相关记录,还没有追到底


  1. String的native String intern()方法源码
    openjdk\jdk\src\share\native\java\lang\String.c

    
    #include "jvm.h"
    
    
    #include "java_lang_String.h"
    
    
    JNIEXPORT jobject JNICALL
    Java_java_lang_String_intern(JNIEnv *env, jobject this)
    {
     return JVM_InternString(env, this);
    }
  2. 在jvm头文件中
    openjdk\jdk\src\share\javavm\export\jvm.h

    /*
    * java.lang.String
    */
    JNIEXPORT jstring JNICALL
    JVM_InternString(JNIEnv *env, jstring str);
  3. 在jvm.cpp文件中
    openjdk\hotspot\src\share\vm\prims\jvm.cpp

    // String support ///////////////////////////////////////////////////////////////////////////
    
    JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
    JVMWrapper("JVM_InternString");
    JvmtiVMObjectAllocEventCollector oam;
    if (str == NULL) return NULL;
    oop string = JNIHandles::resolve_non_null(str);
    oop result = StringTable::intern(string, CHECK_NULL);
    return (jstring) JNIHandles::make_local(env, result);
    JVM_END

你可能感兴趣的:(Java)