String 常量池的使用

来源:互联网 发布:bonjour是什么软件 编辑:程序博客网 时间:2024/06/17 11:22

string constants pool 使用

我们知道 jvm 对于String 有一个常量池的分配. 哪是在什么情况下才会把string 存储到常量池中?

在 jdk1.7 以后已经把常量池从perm gen 迁移到heap(young gen and/or old gen) 里面了。
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html RFE: 6962931

什么情况下放入常量池?

1.在编译期已确定的字符串
2.显示调用String.intern()方法

第二种情况很明确,那第一种情况什么是编译期已确定呢?

  • 来看第一个demo:
public static void main(String[] args){        String s = "sunla";    }

这个demo里面 我们通过javap 查看下 生成的class文件

  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=1, locals=2, args_size=1         0: ldc           #16                 // String sunla         2: astore_1         3: return      LineNumberTable:        line 6: 0        line 7: 3      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       4     0  args   [Ljava/lang/String;            3       1     1     s   Ljava/lang/String;}

可以看到 一个字节码命令 ldc 代表的含义是 把常量池中的项压入栈中.
这就可以知道 在编译时 已经知道”sunla” 字符串 并放入到常量池中.

  • 在看个demo
    public static void main(String[] args){        String s = new String("sunla");    }

通过javap 查看生成的class 文件

  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=2, args_size=1         0: new           #16                 // class java/lang/String         3: dup         4: ldc           #18                 // String sunla         6: invokespecial #20                 // Method java/lang/String."<init>":(Ljava/lang/String;)V         9: astore_1        10: return      LineNumberTable:        line 6: 0        line 7: 10      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      11     0  args   [Ljava/lang/String;           10       1     1     s   Ljava/lang/String;}SourceFile: "StringTest2.java"

可以看到 还是生成了一个 ldc 字节码命令 把”sunla”字符串放入到字符串常量池中,并同时 调用了String 对象的实例构造方法.
那是不是说 通过 new String 产生的都会放入常量池呢?
我们在看过demo

    public static void main(String[] args){        /** 通过UUID 生成随机字符串 */        String s = new String(UUID.randomUUID().toString());    }

我们在来看下class 信息

  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=2, args_size=1         0: new           #16                 // class java/lang/String         3: dup         4: invokestatic  #18                 // Method java/util/UUID.randomUUID:()Ljava/util/UUID;         7: invokevirtual #24                 // Method java/util/UUID.toString:()Ljava/lang/String;        10: invokespecial #28                 // Method java/lang/String."<init>":(Ljava/lang/String;)V        13: astore_1        14: return      LineNumberTable:        line 9: 0        line 10: 14      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      15     0  args   [Ljava/lang/String;           14       1     1     s   Ljava/lang/String;}SourceFile: "StringTest2.java"

可以看到 这个例子中并没有生成 ldc 命令,就说明 没有把uuid生成的字符串放入到常量池中.
这是为什么呢?
因为在代码中是通过调用
invokestatic #18 // Method java/util/UUID.randomUUID:()
静态方法实现的,在编译期并不可知,只有在运行期才可知道.

  • 在看另一个demo
    public static void main(String[] args){        String c = "a" + "b";    }
  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=1, locals=2, args_size=1         0: ldc           #16                 // String ab         2: astore_1         3: return      LineNumberTable:        line 8: 0        line 9: 3      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       4     0  args   [Ljava/lang/String;            3       1     1     c   Ljava/lang/String;}SourceFile: "StringTest2.java"

通过查看class 可以看到在编译成class文件时 jvm使用了合并优化功能,
把字符串”ab” 放入到字符串常量池中.

  • 在看另外一个demo
    public static void main(String[] args){        /** 跟上面实现同样功能 */        String a = "a";        String b = "b";        String c = a + b;    }
  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=4, args_size=1         0: ldc           #16                 // String a         2: astore_1         3: ldc           #18                 // String b         5: astore_2         6: new           #20                 // class java/lang/StringBuilder         9: dup        10: aload_1        11: invokestatic  #22                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;        14: invokespecial #28                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V        17: aload_2        18: invokevirtual #31                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        21: invokevirtual #35                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        24: astore_3        25: return      LineNumberTable:        line 8: 0        line 9: 3        line 10: 6        line 11: 25      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      26     0  args   [Ljava/lang/String;            3      23     1     a   Ljava/lang/String;            6      20     2     b   Ljava/lang/String;           25       1     3     c   Ljava/lang/String;}SourceFile: "StringTest2.java"

从这里可以很明确的看到 只生成了2个ldc 字节码命令 就是把 “a” 和 “b” 存入到常量池中了.
那为什么没有”ab” 的字符串常量呢?
因为在编译期 c引用 指向的String是不可知的,在执行过程中 a 和 b 的引用很有可能变化.
所以并不会生成”ab” 字符串常量

总结 - 可放入常量池的情况

  • 编译期可确定的字符串

    1. String a = “a”;
    2. String a = new String(“a”);
    3. String a = “a” + “b”;
  • 显示调用intern

    1. String s = new String(UUID.randomUUID().toString()).intern();

常量池的实现

有兴趣了解底层实现的同学可以去看看
hotspot源码下载路径 http://openjdk.java.net/groups/hotspot/
代码位置 : hotspot/src/share/vm/classfile/symbolTable.hpp

class StringTable : public Hashtable<oop> {  friend class VMStructs;private:  // The string table  static StringTable* _the_table;  static oop intern(Handle string_or_null, jchar* chars, int length, TRAPS);  oop basic_add(int index, Handle string_or_null, jchar* name, int len,                unsigned int hashValue, TRAPS);  oop lookup(int index, jchar* chars, int length, unsigned int hashValue);  StringTable() : Hashtable<oop>((int)StringTableSize,                                 sizeof (HashtableEntry<oop>)) {}  StringTable(HashtableBucket* t, int number_of_entries)    : Hashtable<oop>((int)StringTableSize, sizeof (HashtableEntry<oop>), t,                     number_of_entries) {}public:  // The string table  static StringTable* the_table() { return _the_table; }  static void create_table() {    assert(_the_table == NULL, "One string table allowed.");    _the_table = new StringTable();  }  static void create_table(HashtableBucket* t, int length,                           int number_of_entries) {    assert(_the_table == NULL, "One string table allowed.");    assert((size_t)length == StringTableSize * sizeof(HashtableBucket),           "bad shared string size.");    _the_table = new StringTable(t, number_of_entries);  }  // GC support  //   Delete pointers to otherwise-unreachable objects.  static void unlink(BoolObjectClosure* cl);  // Invoke "f->do_oop" on the locations of all oops in the table.  static void oops_do(OopClosure* f);  // Probing  static oop lookup(Symbol* symbol);  // Interning  static oop intern(Symbol* symbol, TRAPS);  static oop intern(oop string, TRAPS);  static oop intern(const char *utf8_string, TRAPS);  // Debugging  static void verify();  // Sharing  static void copy_buckets(char** top, char*end) {    the_table()->Hashtable<oop>::copy_buckets(top, end);  }  static void copy_table(char** top, char*end) {    the_table()->Hashtable<oop>::copy_table(top, end);  }  static void reverse() {    the_table()->Hashtable<oop>::reverse();  }};