对象内存模型[1]String

【参考链接】

 

一个String对象在堆上占用几个字节

虽然String对象所代表的字符串可能是多种多样的,但是一个String对象在堆内存中所占用的内存空间大小是固定的,这是因为

为了节省内存空间,一个应用程序中,所有的字符串常量都集中、不重复的保存在一个表中个表叫做字符串拘留表

一个String类型内部包含如下两个成员变量

char[],字符数组,本质是个引用,指向字符串拘留表中的字符串

int字符串的哈希值

同样在64位环境下,关闭压缩,以如下代码为例

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 

 

package com.shadowfaxghh.test;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

public class Main {

    public static void main(String[] args) {

        System.out.println(VM.current().details());

        String str1=new String("abc");
        String str2=new String("abcde");
        System.out.println(ClassLayout.parseInstance(str1).toPrintable());
        System.out.println(ClassLayout.parseInstance(str2).toPrintable());
    }
}

 

输出如下

 

可以看到两个对象占用的内存空间大小是一致的

对象头占16个字节,char[]类型(引用类型)成员变量占8个字节,int类型成员变量占4个字节,填充4个字节,共32字节

 

不同的创建String对象的方式

我们经常会使用如下两种方式来创建String对象但是这两种方式下的内存模型其实是不同的

 Java Code 

1
2
3
4
5
6
7
8
9
10
 

 

package com.shadowfaxghh.test;

public class Main {

    public static void main(String[] args) {
        String str1=new String("abc");
        String str2="abc";
        System.out.println(str1==str2);//false
    }
}

 

通过观察其生成的.class文件中的字节码指令

 

可知他们的内存模型大体如下

 

对于String str1=new String("abc"); 是在堆上创建了一个String对象,设置这个对象,然后这个String引用保存到局部变量中

对于String str2="abc"; 是直接将对运行时数据结构中的String引用保存到局部变量中

故两者是不相同的

 

String类还提供了intern()方法用于获取一个字符串在拘留表中的引用

因为该表中没有重复项,所以任何字面相同的字符串的intern()方法返回总是相等的。以如下代码为例,其输出结果如注释所示

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 

 

package com.shadowfaxghh.test;

public class Main {

    public static void main(String[] args) {
        String a=new String("123");
        String b=new String("123");
//      String a=Integer.toString(1)+Integer.toString(2)+ Integer.toString(3);//效果跟上面一样
        
        String c="123";
        String d="123";
        
        System.out.println(a==b);//false
        System.out.println(a.equals(b));//true
        
        
        System.out.println(a==c);//false
        System.out.println(a.equals(c));//true
        System.out.println(a.intern()==c);//true
        
        System.out.println(c==d);//true
    }
}

 

关于字符串拘留表的位置

JDK1.6之前,字符串拘留表属于方法区的一部分,但是在JDK1.7以后,它被移动到了堆中进行管理可以通过如下代码来验证

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 

 

package com.shadowfaxghh.test;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<String> list=new ArrayList<String>();
        int i=0;
        while(true)
            list.add(String.valueOf(i++).intern());
    }
}

 

在运行时会发生溢出OutOfMemoryError通过在不同的JDK版本下运行,根据错误中描述的位置,可以得知差异。

 

此外,字符串拘留表中的字符串也可能会被回收的

所以,虽然intern()返回的永远等于字符串字面量,但是不代表每时每刻相同字符串的intern()返回的都是一样的

因为存在这样一种可能,在一次intern()调用以后,该字符串被后续被回收了,之后再进行一次intern()调用,那么相同的字符串字面量又重新加入拘留表,但是位置已经不同。

 

不可变性

不变性是指String对象一旦生成,则不能再对他进行改变

他的主要作用在于,当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而提升系统性能。

由于不变性,一些看起来像是修改的操作,其实都是通过产生新的字符串来完成的,比如说String.substring()String.concat()方法

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
 

 

package com.shadowfaxghh.test;

public class Main {

    public static void main(String[] args) {
        String str1=new String("abcde");
        
        String str2=str1.substring(1, 2);
        System.out.println(str1==str2);//false
    }
}

 

此外,我们也经常会使用“+”这样的写法,既然具有不变形,所以这些操作其实也是通过创建StringBuilder对象来完成的

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 

 

package com.shadowfaxghh.test;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        String str1=new String("abcde");
        
        str1=str1+"f";
        
//      //相当于是
//      StringBuilder sb1=new StringBuilder(str1);
//      sb1.append("f");
//      str1=sb1.toString();
        
        str1=str1+"g"+"h";
        
//      //相当于是
//      StringBuilder sb2=new StringBuilder(str1);
//      StringBuilder temp = sb2.append("g");
//      System.out.println(temp==sb2);//StringBuilder是同一个对象//true
//      
//      sb2.append("h");
//      str1=sb2.toString();
    }
}

 

通过其字节码指令可知

是创建了StringBuilder对象,然后持续调用其append()方法,最后调用toString()生成一个新的String对象返回。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值