Java基础 - String

首先,来一道String的考题,代码如下:

public class StringTest {
    
    public static void main(String[] args) {
        String str1 = new String("a") + new String("a");
        str1.intern();
        String str2 = "aa";
        System.out.println(str1 == str2); 
    }
        
}

先不着急给出答案,我们来一步一步得进行分析,争取覆盖String大部分的知识点。

一、new String("a")创建了几个对象?

下面是一段代码只包含new String("a")的代码:

public class String1 {
    public static void main(String[] args) {
        new String("a");
    }
}

编译之后(使用JDK7),我们通过javap来查看编辑器生成的字节码代码。

javap -verbose String1
=====JDK7=====
Classfile /D:/String1.class
  Last modified 2019-1-24; size 336 bytes
  MD5 checksum 44db69143fadc8350a9de0a53c6ef4a7
  Compiled from "String1.java"
public class String1
  SourceFile: "String1.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
   #2 = Class              #16            //  java/lang/String
   #3 = String             #17            //  a
   #4 = Methodref          #2.#18         //  java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #19            //  String1
   #6 = Class              #20            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               String1.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = Utf8               java/lang/String
  #17 = Utf8               a
  #18 = NameAndType        #7:#21         //  "<init>":(Ljava/lang/String;)V
  #19 = Utf8               String1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               (Ljava/lang/String;)V
{
  public String1();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String a
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: pop
        10: return
      LineNumberTable:
        line 4: 0
        line 5: 10
}

1. Constant Pool

编译后的字节码文件在上面已经给出来了,这里我们首先来关注一下Constant Pool。常量池可以理解为Class文件的资源仓库【此时没有加载进内存,也就是在文件中】,用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。字面量比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值。

我们通过观察,发现了一点端倪:


CONSTANT_String_info.png

常量池的CONSTANT_String_info表中存放了字符串类型字面量"a"。

2. 运行时常量池

Class文件中的constant_pool表是常量池的静态描述,虚拟机在对类进行解析和连接之后,将在内存中为该类生成一套运行时常量池【此时存在在内存中】。运行时常量池是类文件中constant_pool表的运行时动态描述。JVM规范规定,常量池在运行期间在方法区中进行分配。

记住,Java虚拟机为每一个装载的类和接口保存一份独立的常量池。当一条指令引用常量池中的第5个元素的时候,它指向的是当前类的常量池中的第5个元素,即定义Java虚拟机当前正执行的方法的类。

运行时常量池是一个特定实现的数据结构,数据结构映射到class文件中的常量池。

3.字符串常量池

CONSTANT_String_info结构包含了由Unicode码点(Code points)序列来组成字符串常量。

JVM规范是这样说的:Java语言需要全局统一的字符常量(这就意味着如果不同字字面量(Literal)包含着相同的码点序列,就必须引用这相同的String类的实例)。

那么如何保证全局统一的字符常量呢?

实现是这样的:

每一个Java虚拟机必须维护一张内部列表,它列出了所有在运行程序的过程中已被"拘留(intern)"的字符串对象的引用。基本上,如果一个字符串在虚拟机的拘留列表上出现,就说它是被拘留的。维护这个列表的关键是任何特定的字符序列在这个列表上只出现一次。

要拘留CONSTANT_String_info入口所代表的字符序列,虚拟机要检查内部拘留名单上这个字符序列是否已经在编了。如果已经在编,虚拟机使用指向以前拘留的字符串对象的引用。否则,虚拟机按照这个字符序列创建一个新的字符串对象,并把这个对象的引用编入列表。

这里就引出了字符串常量池的概念,JVM中开辟了一个特殊的内存区域用于存储字符串常量。配合上面说到的"拘留"的概念来保证JVM规范中规定的全局统一的字符常量。

字符串常量池在JVM运行时数据区的哪个区域中

上面我们知道,字符串常量池中的数据来源于constant_pool中的CONSTANT_String_info表,而constant_pool是常量池的静态描述,运行时常量池是动态描述,而且存储于方法区中,那么字符串常量池是否也存在于方法区中呢?

答案是:这个要看具体的JDK版本。

JDK7之前,JVM把字符串常量池放在PermGen space中,而且常量池大小在运行时不能扩展也不能被垃圾回收。把String字面量拘留在PermGen中,那么当我们拘留了太多的字符串字面量那么就会导致OutOfMemory错误。

所以从JDK7开始,字符串常量池被放置到堆中,这个区域可以被进行垃圾回收。这样做的好处是未被引用的字符串字面量对象可以被进行垃圾回收,从而从字符串常量池中移除,释放了内存,减小了出现OutOfMemmory错误的风险。

通过下面的代码,基于不同的JDK版本来进行测试:

import java.util.List;
import java.util.ArrayList;
/*
 * VM Args: -XX:PermSize=10m -XX:MaxPermSize=10m
 */
public class String3 {
    
    public static void main(String[] args) {
        
        List<String> list = new ArrayList<String>();
        
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
        
    }
    
}

intern()方法后面介绍

运行命令如下:

java -XX:PermSize=10m -XX:MaxPermSize=10m String3

JDK6运行结果:

D:\>java -XX:PermSize=10m -XX:MaxPermSize=10m String3
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        at String3.main(String3.java:12)

JDK7运行结果:

D:\>java -XX:PermSize=10m -XX:MaxPermSize=10m String3
// 程序不停止

小结

经过上面的分析我们可以知道new String("a"),"a"这个字符串字面量在编译之后存储于constant_pool的CONSTANT_String_info表中,class文件被JVM解析后,会创建一个内容为"a"的字符串对象,并把它"拘留"到字符串常量池中。

4. new String("xx")

继续分析上面的字节码,如下:


new.png

new String()编译后对应的字节码指令如上图红框所示,new是创建类实例的指令。类实例对象在堆中分配,也就是说在堆中创建了一个字符串对象。

5.new String("a")创建了2个对象

图示如下(JDK7):


new String(_a_).png

二、两个new String("a")会创建几个对象?

上面我们分析了代码中一个new String("a")的情况,现在我们来通过下面的代码来巩固一下:

public class String2 {
    
    public static void main(String[] args) {
        new String("a");
        new String("a");
    }

}

同上,通过javap查看编译后的字节码内容:

=====JDK7=====
Classfile /D:/String2.class
  Last modified 2019-1-24; size 350 bytes
  MD5 checksum 43b6f1d60b2c6cfe1cf050651dca9eb9
  Compiled from "String2.java"
public class String2
  SourceFile: "String2.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
   #2 = Class              #16            //  java/lang/String
   #3 = String             #17            //  a
   #4 = Methodref          #2.#18         //  java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #19            //  String2
   #6 = Class              #20            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               String2.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = Utf8               java/lang/String
  #17 = Utf8               a
  #18 = NameAndType        #7:#21         //  "<init>":(Ljava/lang/String;)V
  #19 = Utf8               String2
  #20 = Utf8               java/lang/Object
  #21 = Utf8               (Ljava/lang/String;)V
{
  public String2();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String a
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: pop
        10: new           #2                  // class java/lang/String
        13: dup
        14: ldc           #3                  // String a
        16: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        19: pop
        20: return
      LineNumberTable:
        line 4: 0
        line 5: 10
        line 6: 20
}

我们可以看到CONSTANT_String_info只有一项。


同一个引用.png

也就是说字符串常量池中只会有一个内容为"a"的字符串对象。

上面代码中我们调用了两次new指令,也就是在堆中创建了两个字符串对象。

所以两个new String("a")会创建3个对象。图示如下:


new String(_a_) new String(_a_).png

三、重载"+"与StringBuilder

先来看如下代码:

public class String4 {
    
    public static void main(String[] args) {
        String str1 = new String("a") + new String("a");
    }
    
}

算术运算中的"+"符号表示将两个数字进行相加。那么字符串对象中的"+"的作用以及实现原理是怎样的呢?

"+"应用于字符串,表示连接操作。

Java为String对象重载了"+"操作符。

重载的意思是,一个操作符在应用于特定的类时,被赋予了特殊的意义。

用于String的"+"与"+="是Java中仅有的两个重载过的操作符,而Java并不允许程序员重载任何操作符

来看一下,编译后的字节码文件:

=====JDK7=====
Classfile /D:/String4.class
  Last modified 2019-1-24; size 506 bytes
  MD5 checksum 82d81a367f287123ea0846181773fb30
  Compiled from "String4.java"
public class String4
  SourceFile: "String4.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#19        //  java/lang/Object."<init>":()V
   #2 = Class              #20            //  java/lang/StringBuilder
   #3 = Methodref          #2.#19         //  java/lang/StringBuilder."<init>":()V
   #4 = Class              #21            //  java/lang/String
   #5 = String             #22            //  a
   #6 = Methodref          #4.#23         //  java/lang/String."<init>":(Ljava/lang/String;)V
   #7 = Methodref          #2.#24         //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #2.#25         //  java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #26            //  String4
  #10 = Class              #27            //  java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               SourceFile
  #18 = Utf8               String4.java
  #19 = NameAndType        #11:#12        //  "<init>":()V
  #20 = Utf8               java/lang/StringBuilder
  #21 = Utf8               java/lang/String
  #22 = Utf8               a
  #23 = NameAndType        #11:#28        //  "<init>":(Ljava/lang/String;)V
  #24 = NameAndType        #29:#30        //  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #25 = NameAndType        #31:#32        //  toString:()Ljava/lang/String;
  #26 = Utf8               String4
  #27 = Utf8               java/lang/Object
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Utf8               append
  #30 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #31 = Utf8               toString
  #32 = Utf8               ()Ljava/lang/String;
{
  public String4();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
         7: new           #4                  // class java/lang/String
        10: dup
        11: ldc           #5                  // String a
        13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: new           #4                  // class java/lang/String
        22: dup
        23: ldc           #5                  // String a
        25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: return
      LineNumberTable:
        line 4: 0
        line 5: 35
}

观察下图的红框部分:


创建了StringBuilder对象.png

JVM为我们创建了一个StringBuilder对象。

继续观察:


使用了append方法.png

继续观察:


调用了toString()方法.png

StringBuilder类的toString方法如下:

public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
}

这里可以看到最终在堆中创建了一个String对象。

分析总结

字符串对象的"+"操作是通过JVM内部操作StringBuilder类实现的。虽然我们在源代码中并没有使用StringBuilder类,但是编译器却自动主张得使用了它(因为它高效)。在这个例子中,编译器创建了一个StringBuilder对象,用以构造最终的String,并为每个字符串调用一次StringBuilder的append()方法,总共两次。最后调用toString()生成结果,并存为str1(使用的命令为astore_1)。

执行完后(JDK7),字符串缓存区及堆内容如下:

new String(_a_)+new String(_a_).png

"+"巩固

实例1

public class String5 {
    
    public static void main(String[] args) {
        String str1 = "a" + "a";
    }
    
}

执行后字符串常量池和堆中内容是怎样的呢?

查看字节码:

=====JDK7=====
Classfile /D:/String5.class
  Last modified 2019-1-24; size 274 bytes
  MD5 checksum ee94b3059906147f3d62aedd166fb5c3
  Compiled from "String5.java"
public class String5
  SourceFile: "String5.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         //  java/lang/Object."<init>":()V
   #2 = String             #14            //  aa
   #3 = Class              #15            //  String5
   #4 = Class              #16            //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               String5.java
  #13 = NameAndType        #5:#6          //  "<init>":()V
  #14 = Utf8               aa
  #15 = Utf8               String5
  #16 = Utf8               java/lang/Object
{
  public String5();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String aa
         2: astore_1
         3: return
      LineNumberTable:
        line 4: 0
        line 5: 3
}
字符串字面量相加.png

通过字节码信息我们可以看到,对于两个字符串字面量想"+",Java编译器替我们做了优化,合并了两个字符串字面量。

那么在JVM中,就只有字符串常量池中有对象,对象内容为"aa"。

实例2

public class String6 {
    
    public static void main(String[] args) {
        String str1 = new String("a") + "a";
    }
    
}

执行后字符串常量池和堆中内容是怎样的呢?

查看字节码:

Classfile /D:/String6.class
  Last modified 2019-1-24; size 499 bytes
  MD5 checksum f1725842c1d11cb65000110f2ca6c3d7
  Compiled from "String6.java"
public class String6
  SourceFile: "String6.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#19        //  java/lang/Object."<init>":()V
   #2 = Class              #20            //  java/lang/StringBuilder
   #3 = Methodref          #2.#19         //  java/lang/StringBuilder."<init>":()V
   #4 = Class              #21            //  java/lang/String
   #5 = String             #22            //  a
   #6 = Methodref          #4.#23         //  java/lang/String."<init>":(Ljava/lang/String;)V
   #7 = Methodref          #2.#24         //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #2.#25         //  java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #26            //  String6
  #10 = Class              #27            //  java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               SourceFile
  #18 = Utf8               String6.java
  #19 = NameAndType        #11:#12        //  "<init>":()V
  #20 = Utf8               java/lang/StringBuilder
  #21 = Utf8               java/lang/String
  #22 = Utf8               a
  #23 = NameAndType        #11:#28        //  "<init>":(Ljava/lang/String;)V
  #24 = NameAndType        #29:#30        //  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #25 = NameAndType        #31:#32        //  toString:()Ljava/lang/String;
  #26 = Utf8               String6
  #27 = Utf8               java/lang/Object
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Utf8               append
  #30 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #31 = Utf8               toString
  #32 = Utf8               ()Ljava/lang/String;
{
  public String6();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
         7: new           #4                  // class java/lang/String
        10: dup
        11: ldc           #5                  // String a
        13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: ldc           #5                  // String a
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore_1
        28: return
      LineNumberTable:
        line 4: 0
        line 5: 28
}

从字节码中可以看到,字符串对象和字符串字面量相加,JVM同样通过引入StringBuilder进行处理。执行完后,字符串常量池和堆中内容如下:


new String(_a_) + _a_.png

四、String类的intern()方法

作用:用来拘留一个字符串。
返回:一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池

字符串字面量调用intern()

我们知道所有字面上表达的字符串都在解析CONSTANT_String_info入口的过程中被拘留了。也就是说如果字符串是字面量的话,方法的结果应当是对相同字面量的String实例的引用。因此:

("a" + "b" + "c").intern() == "abc"

字符串对象调用intern()

我们来看一下下面的代码:

public class String7 {
    
    public static void main(String[] args) {
        String str1 = new String("a") + new String("a");
        str1.intern();
    }
    
}

根据之前上面的分析,可以知道new String("a") + new String("a")执行后,JVM运行时数据区的内存内容是这样的:


new String(_a_) + new String(_a_).png

str1引用指向堆中内容为"aa"的字符串对象。

现在来分析str1.intern()执行后,字符串常量池和堆中的内容会发生什么样的变化呢?

首先要确定一点,调用intern()方法,第一步会去字符串常量池中查找(lookup)相同内容的字符串对象是否存在,如果存在则直接返回这个对象的引用。

找不到的话怎么办?

先来说,既然是一个字符串对象(str)调用intern(),而字符串常量池中找不到相同内容的字符串对象,则说明(str)引用的对象是放在堆中的。那么下一步就要"拘留"这个字符串对象。

怎么"拘留"?

不同的JDK版本操作是不一样的!

在1.6及以前的JDK中,intern方法会在调用时先去常量池中查看是否有相同的字符串(equals()),如果有那就返回常量的引用,如果没有就复制字符串实例放到常量区,然后再返回对常量的引用。

JDK1.7及以后版本,intern方法不会再复制实例,而是在常量池中记录首次出现的字符串(equals())的实例引用。

再来看String7这段代码。

JDK6中执行,堆中内容如下:

jdk1.6_intern().png

JDK7中执行,堆中内容如下:

jdk1.7_intern().png

分析StringTest输出

根据上面的描述,我们再来分析,文章开头的代码输出:

public class StringTest {
    
    public static void main(String[] args) {
        String str1 = new String("a") + new String("a");
        str1.intern();
        String str2 = "aa";
        System.out.println(str1 == str2); 
    }
        
}

JDK1.6中执行示意图

jdk1.6执行.png

str1和str2分别指向不同的对象,所以结果输出为false。

StringTest_jdk1.6.png

JDK1.7中执行示意图

jdk1.7执行.png

可以看到str1和str2指向的是同一个对象,所以输出为true。

StringTest_jdk1.7.png

五、再谈字符串常量池

通过研究JDK的底层代码可以知道String的String Pool是一个固定大小的Hashtable。

我们以intern本地方法为入口来粗略得看一下。

// ======jdk/src/share/native/java/lang/String.c======
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}

// ======hotspot/src/share/vm/prims/jvm.cpp======
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

//======hotspot/src/share/vm/classfile/symbolTable.cpp======
oop StringTable::intern(oop string, TRAPS)
{
  if (string == NULL) return NULL;
  ResourceMark rm(THREAD);
  int length;
  Handle h_string (THREAD, string);
  jchar* chars = java_lang_String::as_unicode_string(string, length);
  oop result = intern(h_string, chars, length, CHECK_NULL);
  return result;
}

oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  unsigned int hashValue = java_lang_String::hash_string(name, len);
  int index = the_table()->hash_to_index(hashValue);
  oop string = the_table()->lookup(index, name, len, hashValue);

  // Found
  if (string != NULL) return string;

  // Otherwise, add to symbol to table
  return the_table()->basic_add(index, string_or_null, name, len,
                                hashValue, CHECK_NULL);
}

oop StringTable::basic_add(int index, Handle string_or_null, jchar* name,
                           int len, unsigned int hashValue, TRAPS) {
  debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
  assert(!Universe::heap()->is_in_reserved(name) || GC_locker::is_active(),
         "proposed name of symbol must be stable");

  Handle string;
  // try to reuse the string if possible
  if (!string_or_null.is_null() && (!JavaObjectsInPerm || string_or_null()->is_perm())) {
    string = string_or_null;
  } else {
    string = java_lang_String::create_tenured_from_unicode(name, len, CHECK_NULL);
  }

  // Allocation must be done before grapping the SymbolTable_lock lock
  MutexLocker ml(StringTable_lock, THREAD);

  assert(java_lang_String::equals(string(), name, len),
         "string must be properly initialized");

  // Since look-up was done lock-free, we need to check if another
  // thread beat us in the race to insert the symbol.

  oop test = lookup(index, name, len, hashValue); // calls lookup(u1*, int)
  if (test != NULL) {
    // Entry already added
    return test;
  }

  HashtableEntry<oop>* entry = new_entry(hashValue, string());
  add_entry(index, entry);
  return string();
}

//====== hotspot/src/share/vm/utilities/hashtable.inline.hpp ======
inline void BasicHashtable::add_entry(int index, BasicHashtableEntry* entry) {
  entry->set_next(bucket(index));
  _buckets[index].set_entry(entry);
  ++_number_of_entries;
}

是不是看到了我们操作哈希表的时候熟悉的那些字眼:hash_index, hashValue, buckets。

在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快(过多的hash碰撞)。在jdk7中,StringTable的长度可以通过一个参数指定:

-XX:StringTableSize=99991
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容