你真的了解String的创建吗?

起因

在文章的开始之前,有一个问题需要思考。

String s = "MRyan";
String s = new String("MRyan");

以上是String的两种赋值方式,它们有什么区别吗?它们在内存中有几个实例?存储在哪个区域里?实例存储在哪里?字面量存储在哪里?

想要回答这些问题,需要对JVM有一定的了解

狂补JVM基础知识

都知道JVM的内存结构包括堆,虚拟机栈,方法区,程序计数器,本地方法栈。
其中和本文章有关系的
1. 堆:作为整个JVM内存结构中占用最大的一块空间,存放对象实例和数组,它是线程共享的。
2. 虚拟机栈:在线程的生命周期中,参与计算的数据会频繁的入栈和出栈,栈的生命周期和线程一样,栈内的每条数据都是栈帧。在每个java方法被调用的时候,就会创建一个栈帧并入栈,执行完调用在出栈。栈帧包含:局部变量表,操作数栈,动态链接,返回地址。它是线程独占的 。
3. 方法区:又称非堆区,用于存储JVM加载的类型信息,常量,静态变量,代码缓存等数据。在JDK1.7后用元空间替代永久代,随之字符串常量池和静态变量移动到了堆中。它是线程共享的。

其中在JDK1.7以后包括常量池,运行时常量池,字符串常量池都由方法区移动到了堆内存中。

  • 常量池:即class文件常量池,是class文件的一部分,用于保存编译时确定的数据。包括字面量和符号引用。
  • 运行时常量池:它是每个类私有的。每个class文件里的“常量池”在类被加载器加载之后,常量池中的数据就通过映射存放在运行时常量池,由于Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中,这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(也可以通过String.intern()方法可以强制将String放入常量池)。
  • 字符串常量池:HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容。

有了以上知识点,我们可以继续说回String了

回归正题

编写一段代码如下:

package com.mryan.csdn.string;

class Test{
    public static void main(String[] args){
        String s1= "MRyan";
    }
}

声明了一个字面量为“MRyan”的字符串,因为由于String的不可变性(被final修饰)所以字符串"MRyan"作为字面量存储在class文件常量池中。

类加载之后class文件里常量池里大部分数据会被加载到运行时常量池,但是String类型的数据的引用会被同时存在字符串常量池中,实例创建在堆中。
而其实这只是Test类被类加载的时候,并未有启动程序线程,此时“MRyan”的引用已经存放在字符串常量池中,实例存放在堆中。

等主线程开始创建s1变量时,JVM就会到字符串常量池里找,看有没有能equals(“MRyan”)的String。如果找到了,就在虚拟机栈中当前栈帧的局部变量表里创建s1变量,然后把字符串常量池里对MRyan对象的引用复制给s1变量。找不到的话,才会在heap堆重新创建一个对象,然后把引用驻留到字符串常量区。然后再把引用复制栈帧的局部变量表。

画个简图:
在这里插入图片描述


当定义了多个值为"MRyan"的String,情况是什么样的呢?

package com.mryan.csdn.string;

class Test{
    public static void main(String[] args){
        String s1= "MRyan";
        String s2= "MRyan";
        String s3= "MRyan";
        System.out.println(s1==s2);
        System.out.println(s2==s3);
    }
}

输出:
在这里插入图片描述

和刚才一样,其实是局部变量表里三个变量统一指向同一个堆内存地址。
画个简图:

在这里插入图片描述


但如果是用new关键字来创建字符串,情况就不一样了

package com.mryan.csdn.string;

class Test{
    public static void main(String[] args){
        String s1= "MRyan";
        String s2= "MRyan";
        String s3= new String("MRyan");
        System.out.println(s1==s2);
        System.out.println(s2==s3);
    }
}

输出:
在这里插入图片描述

这时候,s1和s2还是和之前一样。但s3不同因为new关键字会在Heap堆申请一块全新的内存,来创建新对象。虽然字面还是"MRyan",但是完全不同的对象,有不同的内存地址。

画个简图:
在这里插入图片描述

到这里文章开篇的问题你应该都能答出来吧。

猜你喜欢

转载自blog.csdn.net/qq_35416214/article/details/107666617