理解字符串常量池(JVM)

大纲

在这里插入图片描述

思考

  1. 如何查看字符串常量池(StringTable)?

    使用 jclasslib 插件打开字节码,选择 常量池 -> 显示所选 -> CONSTANT_String_info,左侧过滤后的内容即为字符串常量池

  2. 字符串常量池、方法区、永久代和元空间的关系?

    方法区是逻辑概念,永久代和元空间是方法区的实现。字符串常量池总是在堆上

    • JDK7之前,方法区的实现是永久代,永久代是堆的一部分,而字符串常量池是永久代的一部分。
    • JDK7时,方法区的实现是永久代,永久代是堆的一部分,字符串常量池也是堆的一部分,字符串常量池和永久代并列。
    • JDK8及之后,方法区的实现是元空间,元空间是操作系统直接内存,字符串常量池还是堆的一部分。
  3. Java中什么是常量?static final 修饰的量就是常量吗?

    • 从字节码层面上看,字段有 ConstantValue 修饰的就是常量(包含对常量池的索引),如下图所示
      在这里插入图片描述
    • 从 Java 源代码层面上看
      • static final 修饰的 int、double 等基本数据类型(不包含 Integer 等包装类)
      • static final 修饰的 String
      • 字面量,例如 1 或者 "abcd"

    注意事项:static final String s = "abc" 中的 s 是常量,但 static final String s = new String("abc") 中的 s 并不是常量,同样 static final Integer num = 1 中的 num 也不是常量。

  4. 字符串拼接问题

    • 只有常量的情况,会存在编译优化,只会将最后生成的字符串添加到字符串常量池,例如 String s = "ab" + "cd" 字符串常量池中只会出现 "abcd"
    • 出现变量的情况,会创建 StringBuilder 对象,通过 append() 来追加字符内容,最终调用 toString() 来创建字符串对象。

测试 intern() 的作用

发现一个 bug,下面的代码如何批量测试时,internTest03() 方法的结果是 false、true。单独测试该方法时,返回结果是 true、true。

public class InternTest {
    @Test
    public void internTest01() {
        String s = new StringBuilder()
                .append('a')
                .append('b')
                .append('c')
                .append('d')
                .toString();

        String intern = s.intern();

        System.out.println(intern == s);//true
    }


    @Test
    public void internTest02() {
        String s0 = "abcd";

        String s = new StringBuilder()
                .append('a')
                .append('b')
                .append('c')
                .append('d')
                .toString();

        String intern = s.intern();

        System.out.println(intern != s);//true
        System.out.println(intern == s0);//true
    }


    @Test
    public void internTest03() {

        String s = new StringBuilder()
                .append('a')
                .append('b')
                .append('c')
                .append('d')
                .toString();

        String intern = s.intern();
        String s0 = "abcd";

        System.out.println(intern == s);//true
        System.out.println(intern == s0);//true
    }

}

测试创建多少个对象的代码


public class HowManyObjectTest {


    @Test
    public void howManyObjectTest01() {
        String s = "abcd";
    }

    @Test
    public void howManyObjectTest02() {
        String s = "ab" + "cd";
    }


    @Test
    public void howManyObjectTest03() {
        String s = new String("abcd");
    }

    @Test
    public void howManyObjectTest04() {
        String s = new String("ab" + "cd");

        // 验证字符串常量池中是否存在某个字符串的方法:
        // 还可以通过jclasslib直接查看字符串常量池中是否出现"ab"
        // String ab = new String("a") + new String("b");
        // String intern = ab.intern();
        // System.out.println(ab == intern);  //true,证明"ab"是通过ab.intern()放入字符串常量池的,之前并不存在。
    }

    @Test
    public void howManyObjectTest05() {
        String s = new String("abcd") + "abcd";
    }


    static String s1 = "aaa";
    static String s2 = "bbb";

    @Test
    public void howManyObjectTest06() {
        String s = s1 + s2;
    }

    static final String s3 = "aaaa";
    static final String s4 = "bbbb";

    @Test
    public void howManyObjectTest07() {
        String s = s3 + s4;
    }


    static final String s5 = new String("aaaaa");
    static final String s6 = new String("bbbbb");

    @Test
    public void howManyObjectTest08() {
        String s = s5 + s6;
    }
}

问题一:new String("abcd") 会创建多少个对象?

 0 new #3 <java/lang/String>
 3 dup
 4 ldc #2 <abcd>
 6 invokespecial #4 <java/lang/String.<init> : (Ljava/lang/String;)V>
 9 astore_1
10 return

上面是 new String("abcd") 的字节码,下面对上面的内容进行解释:

  1. new #3:表示创建一个 java.lang.String 类型的对象(分配对象空间),并将地址压入操作数栈。
    (其中 #3 表示字符串常量池中的索引,此时指向 java.lang.String

    在这里插入图片描述

  2. dup:对操作数栈的栈顶元素进行复制,并压入操作数栈

    在这里插入图片描述

  3. ldc #2:从常量池中加载字符串 “abcd” 对象,并压入操作数栈

    在这里插入图片描述

  4. invokespecial #4:调用 String 类初始化方法 init<>,由于调用含参构造,因此消耗操作数栈中的两个参数
    (从更通用的角度来看,这个 init<> 的逻辑包含 (a)显示赋值、(b)代码块赋值、(c)构造器方法调用

    在这里插入图片描述

  5. astore_1:将初始化完成的 String 对象的引用赋值给局部变量 s

    在这里插入图片描述

问题二:new String("abcd") + "abcd" 会创建多少个对象?

 0 new #5 <java/lang/StringBuilder>
 3 dup
 4 invokespecial #6 <java/lang/StringBuilder.<init> : ()V>
 7 new #3 <java/lang/String>
10 dup
11 ldc #2 <abcd>
13 invokespecial #4 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 ldc #2 <abcd>
21 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
24 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
27 astore_1
28 return
  1. new $5dupinvokespecial #6:创建一个 StringBuilder 对象,并初始化

    在这里插入图片描述

  2. new #3dupldc #2invokespecial #4new String("abcd") 的代码逻辑

    在这里插入图片描述

  3. invokevirtual #7:调用 append() 方法,返回值类型还是 StringBuilder,消耗操作数栈中的两个元素,并返回一个元素

    注意:如果此时发生GC,那么 0x100 对应的 String 对象会被标记为垃圾。因为从 GCRoot 出发不可达。疑问:字符串常量池是否会进行垃圾回收?其中的对象在什么情况下会进行垃圾回收?

    在这里插入图片描述

  4. ldc #2:直接压入字符串常量池中地址
    在这里插入图片描述

  5. invokevirtual #7:再次调用 append() 方法

    在这里插入图片描述

  6. invokespecial #8:调用 toString() 方法,创建 String 对象

    在这里插入图片描述

  7. astore_1:赋值给局部变量 s
    在这里插入图片描述

其它问题

  1. 验证 String s = "ab" + "cd" 执行完成后,字符串常量池中并不存在 "ab""cd"
  2. 使用 StringBuilder 对象调用 append() 逐个追加字符,最终得到的字符串不会放入到字符串常量池中
  3. 使用 char[] 来创建 String 对象,得到的字符串不会放入到字符串常量池中,StringBuilder 本质上维护了一个动态扩容的 char[]
  4. static final Integer num = 1static final String s = new String("abcd") 中的 num 和 s 均不是常量(可以通过 clinit<> 的代码逻辑进行查看)
  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值