常量(StringTable)池

二进制字节码的组成

二进制字节码的组成:类的基本信息(构造方法,接口的定义)、常量池、静态变量、类的方法定义(包含了虚拟机指令)

通过反编译来查看类的信息

  • javac获得对应类的.class文件
  • 在控制台输入 javap -v 反编译得到类的信息

类的基本信息

常量池

虚拟机中执行编译的方法
(框内的是真正编译执行的内容,#号的内容需要在常量池中查找)

常量池与运行时常量池

  • 常量池:就是一张表(如上图中的constant pool),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
  • 运行时常量池:常量池是*.class文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

常量池与字符串池StringTable的关系

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder,拼接后在堆中
  • 字符串常量拼接的原理是编译器优化,拼接后在串池中
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中,如果对象已存在,返回已存在对象的地址

注意: 无论是串池还是堆里面的字符串,都是对象

字符串进入串池案例

字符串赋值

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

常量池中的信息,都会被加载到运行时常量池中,但这时a b ab 仅是常量池中的符号,还没有成为java字符串

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

当执行到 ldc #2 时,会把符号 a 变为 “a” 字符串对象,并放入串池中
当执行到 ldc #3 时,会把符号 b 变为 “b” 字符串对象,并放入串池中
当执行到 ldc #4 时,会把符号 ab 变为 “ab” 字符串对象,并放入串池中

最终StringTable [“a”, “b”, “ab”]

注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且在串池中不存在的时候(如 ldc #2)时,该字符串才会被创建并放入串池中。

使用拼接字符串变量对象创建字符串的过程

public class StringTableStudy {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
        //拼接字符串对象来创建新的字符串
        String ab2 = a+b; //实际StringBuilder拼接形成
    }
}

反编译后的结果

Code:
      stack=2, locals=5, args_size=1
         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: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: return

通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()最后的toString方法的返回值是一个新的字符串,虽然字符串的值和拼接的字符串一致,但是两个不同的字符串,一个存在于串池之中,一个存在于堆内存之中

String ab = "ab";
String ab2 = a+b;
//结果为false,因为ab是存在于串池之中,ab2是由StringBuilder的toString方法所返回的一个对象,存在于堆内存之中
System.out.println(ab == ab2);

使用拼接字符串常量对象的方法创建字符串

public class StringTableStudy {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
        String ab2 = a+b;
        
	//使用拼接字符串常量的方法创建字符串
        String ab3 = "a" + "b";//ab3直接从串池中获取值,相当于ab3="ab"
    }
}

反编译后的结果

Code:
      stack=2, locals=6, args_size=1
         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: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        //ab3初始化时直接从串池中获取字符串
        29: ldc           #4                  // String ab
        31: astore        5
        33: return

使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已在编译期确定为ab,而创建ab的时候已经在串池中放入了“ab”,所以ab3直接从串池中获取值,所以进行的操作和 ab = “ab” 一致。

使用拼接字符串变量的方法来创建新的字符串时,因为内容是变量,只能在运行期确定它的值,所以需要使用StringBuilder来创建

intern方法 1.8

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,则放入成功
  • 如果有该字符串对象,则放入失败

无论放入是否成功,都会返回串池中的字符串对象

注意:此时如果调用intern方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

例1:

public class Main {
    public static void main(String[] args) {
        //"a" "b" 被放入串池中,str则存在于堆内存之中
        String str = new String("a") + new String("b");
        //调用str的intern方法,这时串池中没有"ab",则会将该字符串对象放入到串池中,此时堆内存与串池中的"ab"是同一个对象
        String st2 = str.intern();
        //给str3赋值,因为此时串池中已有"ab",则直接将串池中的内容返回
        String str3 = "ab";
        //因为堆内存与串池中的"ab"是同一个对象,所以以下两条语句打印的都为true
        System.out.println(str == st2);
        System.out.println(str == str3);
    }
}

例2:

public class Main {
    public static void main(String[] args) {
        //此处创建字符串对象"ab",因为串池中还没有"ab",所以将其放入串池中
        String str3 = "ab";
        //"a" "b" 被放入串池中,str则存在于堆内存之中
        String str = new String("a") + new String("b");
        //此时因为在创建str3时,"ab"已存在于串池中,所以放入失败,但是会返回串池中的"ab"
        String str2 = str.intern();
        //false,str在堆内存,str2在串池
        System.out.println(str == str2);
        //false,str在堆内存,str3在串池
        System.out.println(str == str3);
        //true,str2和str3是串池中的同一个对象
        System.out.println(str2 == str3);
    }
}

StringTable 垃圾回收

StringTable在内存紧张时,会发生垃圾回收

StringTable调优

  • 因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间
java -XX:StringTableSize=20000 //可调整大小
  • 考虑是否需要将字符串对象入池
  • 可以通过intern方法减少重复入池
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值