Java字符串的内存结构

问题提出

若是问到String s = new String(“abc”);这条语句执行过程中Java虚拟机JVM创建了几个对象,想必很多人都知道,两个。但是如果是这样String s = “abc”;那么JVM又创建了几个对象呢?

问题延申

近期学习Java字符串过程中遇到这个问题,愈想愈发觉得有趣。首先Java中String的存储是通过一个char[]类型的成员变量value进行存储的,所以String s = “abc”;这个过程中JVM也应该创建了两个对象,不过一个是s指向的String对象,另一个是s的value成员执行的char[]对象。

那么问题又来了,

public class test{
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = new String(s1);
        String s5 = new String(new char[]{'a', 'b', 'c'});
    }
}

上面这个过程中三个字符串s1、s2和s3之间的内存结构以及它们的value成员的内存结构有什么联系呢?

首先,String s1 = “abc”;这种方式通过字面量创建一个String对象,并将它的地址赋给s1,而这种方式通过字面量创建的对象存在方法区的常量池中,而常量池有个特点,即常量池中不存在重复字符串,因此字符串s1和s2实际上是指向同一个内存区域的同一个String对象,那也必然它们俩的value成员也是同一个char[]对象。

其次,通过new String(“abc”)的方式则是在堆空间中创建了一个String对象,而且该字符串的声明同样是以字面量的方式声明然后传递给new的,所以该对象的value成员变量也指向的是方法区的常量池中的char[]对象,因此s1、s2和s3的value成员指向的是同一个char[]对象。

再次,类似于s3的创建过程,s4创建的String对象也必定在堆空间中,但是由于传递给构造函数的是一个已有的String对象,参考该构造函数的源码可知,s4与s1的value成员也指向同一个char[]对象。

最后,同样参考String构造函数源码得到,s5的创建过程中,先在堆空间创建了一个char[]对象,然后再使用此char[]对象的拷贝在堆空间中再创建String对象。

因此整个过程中,内存空间可以描述为
在这里插入图片描述

验证过程

对象唯一标志

想要验证两个对象是不是指向同一个内存区域,通过它们的内存地址可以很容易的得到结果,但是Java中没有办法获得对象的内存地址。但是无妨,在Object类中的hashCode()方法可以根据一个对象返回一个整数,这个整数也相当于起到了内存地址的作用,
在这里插入图片描述
但是由于String重写了该方法,因此通过System.identityHashCode(Object)来使用Object中的该方法。

获取String的value成员

由于String类中的value成员是private的,因此还需要修改jdk库中的String.class文件,将value的权限修饰符改为public。

首先从\jdk\src.zip\java\lang\String.java这个路径中将String.java源文件解压到桌面,\jdk表示安装的jdk路径,并将String.java源文件中String类下的value成员的权限修饰符修改为public,使用如下命令编译修改后的java文件,

Desktop>javac String.java

完成此步后会就得到了三个class文件的String.calss、String$1.class和String$CaseInsensitiveComparator.class。

然后将\jdk\jre\lib\rt.jar文件解压到桌面,使用压缩软件就可以完成解压,将刚才得到的三个class文件替换解压后的\Desktop\rt\java\lang路径下的对应文件,在rt目录下使用命令

Desktop\rt>jar cvf rt.jar ./

将这些class文件打包为rt.jar文件,在使用这个命令时需要注意,先不要变动\jdk\jre\lib\rt.jar文件,一切操作以副本进行,否则jar命令可能不能使用。

最后将打包得到的rt.jar文件替换\jdk\jre\lib\路径下的该文件,需要注意备份原始的rt.jar文件,以便后续恢复,完成此步后,String类中value成员的权限修饰符就改为了public,可以进行访问。

代码验证

public class test{
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = new String(s1);
        String s5 = new String(new char[]{'a', 'b', 'c'});

        System.out.println("s1的hashcode:\t\t" + System.identityHashCode(s1));
        System.out.println("s1.value的hashcode:\t" + System.identityHashCode(s1.value));
        System.out.println("s2的hashcode:\t\t" + System.identityHashCode(s2));
        System.out.println("s2.value的hashcode:\t" + System.identityHashCode(s2.value));
        System.out.println("s3的hashcode:\t\t" + System.identityHashCode(s3));
        System.out.println("s3.value的hashcode:\t" + System.identityHashCode(s3.value));
        System.out.println("s4的hashcode:\t\t" + System.identityHashCode(s4));
        System.out.println("s4.value的hashcode:\t" + System.identityHashCode(s4.value));
        System.out.println("s5的hashcode:\t\t" + System.identityHashCode(s5));
        System.out.println("s5.value的hashcode:\t" + System.identityHashCode(s5.value));
    }
}

下面是输出结果。

s1的hashcode:		460141958
s1.value的hashcode:	1163157884
s2的hashcode:		460141958
s2.value的hashcode:	1163157884
s3的hashcode:		1956725890
s3.value的hashcode:	1163157884
s4的hashcode:		356573597
s4.value的hashcode:	1163157884
s5的hashcode:		1735600054
s5.value的hashcode:	21685669

最后,完成测试之后需要记得恢复原始的rt.jar文件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值