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” 字符串常量
总结 - 可放入常量池的情况
编译期可确定的字符串
- String a = “a”;
- String a = new String(“a”);
- String a = “a” + “b”;
显示调用intern
- 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();
}
};