欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

荐 你真的了解String的创建吗?

程序员文章站 2022-04-21 10:54:04
起因在文章的开始之前,有一个问题需要思考。String s = "MRyan";String s = new String("MRyan");以上是String的两种赋值方式,它们有什么区别吗?它们在内存中有几个实例?存储在哪个区域里?实例存储在哪里?字面量存储在哪里?想要回答这些问题,需要对JVM有一定的了解狂补JVM基础知识都知道JVM的内存结构包括堆,虚拟机栈,方法区,程序计数器,本地方法栈。其中和本文章有关系的1. 堆:作为整个JVM内存结构中占用最大的一块空间,存放对象实例和...

起因

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

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堆重新创建一个对象,然后把引用驻留到字符串常量区。然后再把引用复制栈帧的局部变量表。

画个简图:
荐
                                                        你真的了解String的创建吗?


当定义了多个值为"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);
    }
}

输出:
荐
                                                        你真的了解String的创建吗?

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

荐
                                                        你真的了解String的创建吗?


但如果是用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);
    }
}

输出:
荐
                                                        你真的了解String的创建吗?

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

画个简图:
荐
                                                        你真的了解String的创建吗?

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

本文地址:https://blog.csdn.net/qq_35416214/article/details/107666617

相关标签: Java jvm