JVM -- StringTable

常量池最初存在于字节码文件中,在运行时被加载到运行时常量池中,其中a,b等就仅仅是常量池中的符号,还未变成Java中的字符串对象。等到执行到引用这个字符串的代码,才会变成字符串对象 。

执行 ldc #2 会把 a 符号变为“a”字符串对象

执行 ldc #3 会把 b 符号变为“b”字符串对象

创建的过程中,会先到StringTable中找,如果有就共用,没有再创建

StringTable是一个 HashTable 结构,不能扩容

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

取出字符串a

存放到局部变量表1位置

取出字符串b

存放到局部变量表2位置

取出字符串ab

存放到局部变量表3位置

0: ldc           #2                  // String a
2: astore_1
3: ldc           #3                  // String b
5: astore_2
6: ldc           #4                  // String ab
8: astore_3
9: return
public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4=s1+s2;
   //new StringBuilder().append("a").append("2").toString()  new String("ab")
        System.out.println(s3==s4);//false
    }
}

反编译后的结果

 9行,new了一个StringBuilder()对象,13,调用了一个<init>(),无参构造

16,加载局部量表中的1号变量。17,调用SB的append方法

20,加载局部变量表中2号变量。21,再次调用append方法

24,调用sb的toString方法,输出字符串

 StringBuilder的toString方法,返回一个新的String对象。

因此,s3 == s4 为false,虽然他俩指向的均为ab,但是s3的ab是在串池中的,s4的是在堆中的

public class HelloWorld {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4=s1+s2;//new StringBuilder().a|ppend("a").append("2").toString()  new String("ab")
        String s5="a"+"b";
        System.out.println(s5==s3);//true
    }
}

 编译后的结果

29行对应的s5 = "a" + "b"

6行对应的s3 = "ab" 

javac在编译期间的优化,在编译期间就给你加好了。

String str = new String("a"),这行代码创建的对象个数因StringTable中有没有“a”对象而异,如果字符串池有“a”,则此时只会创建一个对象:也就是new的一个字符串对象,存放在堆中;如果没有就会创建两个对象,一个是new的对象存放在堆中,一个是“a”字符串常量对象,存放在StringTable中

StringTable 特性

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是编译器优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回
      • 所以1.6时,s对象不会被放入串池
public class Main{

    public static void main(String[] args){
    
        String s = new String("a") + new String("b");
        s2 = s.intern();
        System.out.println(s2 == "ab");   // true
        System.out.println(s == "ab");  // true
        
    }    

}

a被放在串池中,b也被放入串池中

new String("a") 和new String("b")的对象在堆中,虽然值相同,但是地址不同。

因为此时的"ab"是利用StringBuilder拼接而成,并不是字面量,所以"ab"不会放入串池中,

s是由StringBuilder的toString创建的,是new String("ab"),只在堆中有,串池中没有ab这个值

s.intern(),将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入。并且将串池中的对象返回。

所以s2中存储的是串池中的ab,返回结果为true。

而且已经把s放入串池了,因此也为true。

public class Main{

    public static void main(String[] args) {
        String x = "ab";
        String s = new String("a") + new String("b");

        String s2 = s.intern();//将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,这两种情况都会把串池中的对象返回
        System.out.println(s2 == x);//true
        System.out.println(s == x);//false
    }
}

再看这个代码

现在串池中已经有ab对象了,s2此时存储的是串池中的ab,而s存储的是堆中的。

下面换成1.6

 StringTable 位置 

在这里插入图片描述

1.7之前,StringTable跟随方法区位于永久代,之后就从永久代改为了堆中,因为永久代垃圾回收并不频繁,要等到FullGC才能被回收,即老年代满的时候才会触发,但是StringTable使用的相当频繁,因为有大量的字符串需要存储,如果他的回收效率不高,就会占用大量的内存,会造成永久代的内存不足,因此转移到堆中,仅仅minorGC就可以触发回收。

 1.6方法区的内存溢出,说明字符串常量池位于永久代

 这个错误是因为,如果一次GC,GC结束了,还有98%的内存被占用,就会被JVM视为无可救药了,直接抛异常了。

 加入这个参数

 发现堆空间不足,说明1.8使用的是堆空间

StringTable 垃圾回收

-Xmx10m 指定堆内存大小
-XX:+PrintStringTableStatistics 打印字符串常量池信息
-XX:+PrintGCDetails
-verbose:gc 打印 gc 的次数,耗费时间等信息

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Code_05_StringTableTest {

    public static void main(String[] args) {
        int i = 0;
        try {

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }

}

字符串常量池的信息;

        底层是用哈希表实现的,哈希表底层是数组加链表的结构,数组中的每个位置被成为桶,即buckets,entries即键值对的个数,literals即字符串常量的个数 

 我们修改代码

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Code_05_StringTableTest {

    public static void main(String[] args) {
        int i = 0;
        try {
            for(int j = 0; j < 10000; j++) { // j = 100, j = 10000
                String.valueOf(j).intern();
                i++;
            }

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }

}

在try块中加入一个循环,生成100个字符串,并加入到字符串常量池中。

我们发现确实加了100个字符串常量

我们把循环次数加到10000

 

 发生了GC,且字符串常量变少了

StringTable 性能调优

因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少发生Hash碰撞的次数,来增加HashTable的插入速度

我们设计一个方法,从文件中读取信息,并将信息存入常量池中,再计算一下一共花费了多长时间。

花费0.4秒,数组长度为20万

 我们去掉这个参数

 变成了0.6秒

执行下列程序:

 因为没有出现字面量,所以其中的字符串均不会入池

我们观测一下内存中的数据 

添加一个入池操作

 内存占用显著变少了

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值