JVM学习之从JVM角度解释String

374 阅读3分钟

一、String的基本特性

1、底层结构

  • jdk1.8及以前,采用char[] 数组保存
  • jdk1.9及以后,采用byte[]数据保存

2、不可变性

  • 当对字符串重新复制时,需要重写指定内存区域复制,不能使用原有的value进行赋值
  • 当堆现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
  • 当调用String的replace()方法修改指定字符或字符串时,需要重新指定内存区域赋值,不能使用原有的value进行赋值

3、字符串常量池不能存储相同内同的字符串

  • jdk1.6中字符串常量池的大小为1009,jdk1.7及以后大小为60013
  • -XX:StringTableSize=100,可以通过这个参数修改字符串常量池的大小

二、String的内存分配

1、变迁史

关于字符串常量池的变迁史可以参考方法区的变迁史。

为什么会有这个变迁史?
(1)永久代的默认比较小
(2)永久代的垃圾回收频率低

三、String的基本操作

四、字符串拼接操作

先上结论:

  • 常量与常量的拼接结果在常量池,原理是编译器优化
  • 常量池中不会存在相同内容的常量
  • 只要其中一个是变量,结果就在堆中,拼接的原理是StringBuilder
  • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址

下面是一下例子:

@Test
    public void test1(){
        String str1 = "a" + "b";
        String str2 = "ab";
        System.out.println(str1 == str2); //true
        System.out.println(str1.equals(str2)); //true
    }

    @Test
    public void test2(){
        String s1 = "java";
        String s2 = "Python";
        String s3 = "javaPython";
        String s4 = s1 + "Python";
        String s5 = s1 + s2;
        String s6 = s4.intern();

        System.out.println(s3 == s4); //false
        System.out.println(s3 == s5); //false
        System.out.println(s3 == s6); //true
    }

	/*
	 s1+s2由于是变量拼接,底层调用的是StringBuilder,返回前调用StringBuilder.toString()
	*/

    @Test
    public void test3(){
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1+s2;

        System.out.println(s3 == s4); //false
    }

五、intern()的使用

1、先说两个问题

在说intern()方法之前,先说两个问题:

1new String("ab")会创建几个对象? 答案:22new String("a")+new String("b")会创建几个对象?答案:6

先说第一个:

对象1new关键字在堆空间创建的
对象2:字符串常量池中的ab

有了第一个的基础,再说第二个:

对象1:由于是变量拼接,所以会先new StringBuilder()
对象2new String("a"),堆中
对象3:常量池中的“a”
对象4new String("b"),堆中
对象5:常量池中的“b”
对象6:通过StringBuilder.toString()方法创建的 new String("ab"),特别注意这个在字符串常量池中没有创建“ab”

2、intern()方法的使用结论

先说intern()方法的使用结论:

  • jdk1.6中,将这个字符串对象尝试放入字符串常量池
    • 如果字符串常量池中(永久代)有字符串对象,则不会放入,返回的是已有字符串常量池中的对象的地址
    • 如果字符串常量池中没有字符串对象,会把此对象复制一份放入常量池,并返回常量池中的对象地址
  • jdk1.7及以后,将这个字符串对象尝试放入字符串常量池
    • 如果字符串常量池(堆)中有字符串对象,则不会放入,返回的是已有字符串常量池中的对象的地址
    • 如果字符串常量池中没有字符串对象,则会把对象的引用地址复制一份放入常量池,并返回常量池中的引用地址

3、补充使用例子

先上代码:

String s1 = new String("a") + new String("b");
String s2 = s1.intern();
System.out.println(s1 == s2); //jdk1.6 false jdk1.7 true

上面的代码在不同jdk版本中,得到的结果不同。