【JVM内存结构】String在JVM中的深度剖析(三)

思考题
1、常量池有多大呢?

我想大家一定和我一样好奇,常量池到底能存储多少个常量?
前面我们说过,常量池本质上是一个hash表,这个hash表示不可动态扩容的。也就意味着极有可能出现单个 bucket 中的链表很长,导致性能降低。

在JDK1.8中,这个hash表的固定Bucket数量是60013个,我们可以通过下面这个参数配置指定数量
-Xmx10m 指定堆空间大小
可以增加下面这个虚拟机参数,来打印常量池的数据。

-xx:StringTableSize=‘桶的个数’,如果常量过多,可以添加这个,减少hash冲突,增强性能。

-XX:+PrintStringTableStatistics
//打印垃圾回收信息
-XX:+PrintGCDetails -verbose:gc
增加参数后,运行下面这段代码

public class StringExample {    
    private int value = 1;    
    public final static int fs=101;   
    
    public static void main(String[] args) {    
        final String a="ab";        
        final String b="a"+"b";        
        String c=a+b;    
    }
 }

在JVM退出时,会打印常量池的使用情况如下:

SymbolTable statistics:
Number of buckets    :  20011 = 160088 bytes, avg 8.000
Number of entries    :  12192 = 292608 bytes, avg 24.000
Number of literals   :  12192 = 470416 bytes, avg 38.584
Total footprint      :       = 923112 bytes
Average bucket size      :     0.609
Variance of bucket size  :     0.613
Std. dev. of bucket size :     0.783
Maximum bucket size      :         6

StringTable statistics:
Number of buckets   :   60013  =  480104 bytes, avg  8.000
Number of entries   :     889  =  21336 bytes, avg  24.000
Number of literals  :     889  =  59984 bytes, avg  67.474
Total footprint     :          =    561424 bytes
Average bucket size      :   0.015
Variance of bucket size  :   0.015
Std. dev. of bucket size :   0.122
Maximum bucket size      :       2

可以看到字符串常量池的总大小是60013,其中字面量是889

2、字面量是什么时候进入到字符串常量池的?
  1. 字符串字面量和其他基本类型的字面量或常量不同,并不会在类加载中的解析(resolve)阶段填充并驻留在字符串常量池中,而是以特殊的形式存储在 运行时常量池(Run-Time Constant Pool)中。

  2. 而是只有当此字符串字面量被调用时(如对其执行ldc字节码指令,将其添加到栈顶),HotSpot VM才会对其进行resolve,为其在字符串常量池中创建对应的String实例,懒加载机制

  3. 具体来说,应该是在执行ldc指令时(该指令表示int、float或String型常量从常量池推送至栈顶)

  4. 在JDK1.8的HotSpot VM中,这种未真正解析(resolve)的String字面量,被称为pseudo-string(伪字符串),以JVM_CONSTANT_String的形式存放在运行时常量池中,此时并未为其创建String实例。

  5. 在编译期,字符串字面量以"CONSTANT_String_info"+"CONSTANT_Utf8_info"的形式存放在class文件的 class常量池(Constant Pool) 中;

  6. 在类加载之后,字符串字面量以"JVM_CONSTANT_UnresolvedString(JDK1.7)"或者"JVM_CONSTANT_String(JDK1.8)"的形式存放在 运行时常量池(Run-time Constant Pool) 中;

  7. 在首次使用某个字符串字面量时,字符串字面量以真正的String对象的方式存放在 字符串常量池(String Pool) 中。

通过下面这段代码可以证明。

public static void main(String[] args) {  
    String a = new String(new char[]{'a','b','c'});  
    String b = a.intern();  
    System.out.println(a == b);  // true
    String x = new String("def");  
    String y = x.intern();  
    System.out.println(x == y);  //false
}

使用new char[]{'a','b','c'}构建的字符串,因为并不是字面量,所以并没有在编译的时候使用常量池,而是在调用a.intern()时,将abc保存到常量池并返回该常量池的引用。

3、intern()方法

在Integer中的valueOf方法中,我们可以看到,如果传递的值i是在IntegerCache.low和IntegerCache.high范围以内,则直接从IntegerCache.cache中返回缓存的实例对象。

public static Integer valueOf(int i) {  
    if (i >= IntegerCache.low && i <=IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)]; 
    return new Integer(i);
 }
4、那么,在String类型中,既然存在字符串常量池,那么有没有方法能够实现类似于IntegerCache的功能呢?

答案:intern()方法。由于字符串池是虚拟机层面的技术,所以在String的类定义中并没有类似IntegerCache这样的对象池,String类中提及缓存/池的概念只有intern() 这个方法。

/**     
    * Returns a canonical representation for the string object.     
    * &lt;p&gt;     * A pool of strings, initially empty, is maintained privately by the     
    * class {@code String}.     * &lt;p&gt;     
    * When the intern method is invoked, if the pool already contains a     
    * string equal to this {@code String} object as determined by     
    * the {@link #equals(Object)} method, then the string from the pool is     
    * returned. Otherwise, this {@code String} object is added to the     
    * pool and a reference to this {@code String} object is returned.     
    * &lt;p&gt;     
    * It follows that for any two strings {@code s} and {@code t},     
    * {@code s.intern() == t.intern()} is {@code true}     
    * if and only if {@code s.equals(t)} is {@code true}.     * &lt;p&gt;     
    * All literal strings and string-valued constant expressions are     
    * interned. String literals are defined in section 3.10.5 of the     
    * &lt;cite&gt;The Java&amp;trade; Language Specification&lt;/cite&gt;.     *     
    * @return  a string that has the same contents as this string, but is     
    *          guaranteed to be from a pool of unique strings.     
    */public native String intern();

这个方法(intern() 方法)的作用是:去拿String的内容去Stringtable里查表,如果存在,则返回引用,不存在,就把该对象的"引用"保存在Stringtable表里。

比如下面这段程序:

public static void main(String[] args) {  
    String str = new String("Hello World");  
    String str1=str.intern();  
    String str2 = "Hello World";  
    System.out.print(str1 == str2);//true
}

运行的结果为:true。

实现逻辑如下图所示,

  1. str1通过调用str.intern()去常量池表中获取Hello World字符串的引用。
  2. 接着str2通过字面量的形式声明一个字符串常量。
  3. 由于此时Hello World已经存在于字符串常量池中,所以同样返回该字符串常量Hello World的引用。
  4. 使得str1和str2具有相同的引用地址,从而运行结果为true。
    在这里插入图片描述

intern小结

总结:intern方法会从字符串常量池中查询当前字符串是否存在,并尝试将对象放入串池:

  • 若不存在就会将当前字符串放入常量池中,并返回当前字符串地址引用。
  • 如果存在就返回字符串常量池中字符串的地址。

注意,所有字符串字面量在初始化时,会默认调用intern()方法。

public static void main(String[] args) {  
    String a="Hello";  
    String b="Hello";
}

这段程序,之所以a==b,是

  1. 因为声明a时,会通过intern()方法去字符串常量池中查找是否存在字符串Hello.
  2. 由于不存在,则会创建一个。
  3. 同理,变量b也同样如此,所以b在声明时,发现字符常量池中已经存在Hello的字符串常量,所以直接返回该字符串常量的引用。
  4. 所以地址相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值