String 常量池的使用

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();
  }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值