小心!用String写代码可能会内存泄漏!

2,346 阅读5分钟

目录

  • String字符串在内存里是如何存储的?
  • String.intern()方法
  • String字符串是如何引发内存泄漏的?
  • 总结

今天给大家聊聊咱们平时写代码的时候,最常见的String字符串代码,它的一些底层原理,以及使用不当可能引发的内存泄漏的问题,相信对于大家平时日常开发写代码会有一定的帮助。

String字符串在内存里是如何存储的?

首先呢,当我们平时在代码中写下一行String类型的代码时,大家知道这个String字符串在内存里是如何存储的吗?比如这样的一行代码:String username = "zhangsan",这个"zhangsan"其实就是一串字符串,实际上它在底层是用一个数组来存放的,而且这个数组大小就严格等于这个字符串的长度,它是不可变的,如下图。

接着呢,对于Java中的字符串来说,有一个常量池的概念,意思就是说,对于相同的字符串内容,它往往会在内存里用同一个数组来表示,而不会对相同的字符串内容创建出不同的数组来存放,比如说下面两行代码,大家看看:

String username = "zhangsan"; 
String nickname = "zhangsan";

上面的username和nickname它们两个字符串指向的内容都是"zhangsan",实际上在底层都是用同一个数组来存放的,如下图所示。

所以说,正是因为相同的字符串是引用的同一个底层的数组,所以如果用类似于System.out.println(username == nickname)这种判断代码的话,会发现username == nickname返回的是true,因为它们俩就是指向了底层同一个数组的。

另外再给大家普及一个字符串的知识点,那就是如果我们用一个字符串创建一个String对象的话,那它在内存里一定是另外的一个对象了,如下代码所示,大家看看:

String username = "zhangsan"; 
String nickname = new String("zhangsan"); 
System.out.println(username == nickname);

大家看上面代码,**此时username和nickname比较还是返回true吗?**那不可能的,此时一定是false,因为此时在内存里,username是指向一个数组的,但是nickname是指向一个String对象的,只不过这个String对象里面是有一个"zhangsan"字符串而已,如下图。

但是这个时候又给大家再次介绍一个知识点了,那就是这个String对象内部的"zhangsan"字符串,是怎么存储的呢?其实啊,这个String对象内部的"zhangsan"字符串还是引用了之前的那个数组的,如下图所示。

String.intern()方法

所以说,如果此时你用String.intern()方法,就会发现你可以拿到String对象里的"zhangsan"字符串,此时再用这个字符串做比较,还是返回的是true,大家看下面代码就懂了。

String username = "zhangsan"; 
String nickname = new String("zhangsan");
System.out.println(username == nickname.intern()); // 返回的是true

String字符串是如何引发内存泄漏呢?

好,那么大家都理解了Java里字符串的基本原理后,我们就可以来给大家讲讲平时我们用字符串String写代码,一旦要是不注意,是如何引发内存泄漏问题的。这个问题主要是出现在Java 6以及之前的版本里,在这个较为旧的Java版本中,String.substring()这种字符串截取动作,是会导致内存泄漏的,什么意思呢,我们来看看。

在Java 6以前的版本中,当你调用String.substring()进行字符串截取的时候,它在底层的运作模式是这样的,它会把你的原字符串的数组直接拷贝一份过来,然后用一个offset指针和count标记,来表明截取后的字符串你是需要哪些,如下图所示。

可是在这种运作模式下就有一个问题了,就是你每次substring都会把原数组拷贝一份,可是对于你的子字符串来说仅仅是需要里面的一部分而已,而你缺把原字符串每次都拷贝一份,导致了子字符串中不需要的那部分拷贝内容都是浪费掉的,如下图红圈部分都是子字符串不需要的。

所以此时子字符串不需要的红圈部分处的内容还依然占据了内存,这属于什么问题呢?就是典型的内存泄漏了,也就是说,你要是大量的进行substring一类的操作,就可能会大量的拷贝字符串数组,然后很多拷贝后的字符串数组里,很多内容都是不需要用的,结果还占据了很多内存空间,这就叫做内存泄漏。

内存泄漏指的就是你很多内存空间被占用了,结果你又不用它,别人也没法用,就是典型的占着茅坑不拉屎的行为。

所以后来在Java 7版本开始就对String.substring()进行了源码重构,开始改造了这部分的实现,每次你执行String.substring,它是把原字符串数组中你需要的那部分拷贝过来就可以了,就避免了每次都重复的拷贝原字符串数组,如下图。

总结

虽然在Java 7以及往后的新版本中已彻底的解决了substring导致的内存泄漏问题了,但大家平时在用字符串做开发的过程中,也一定要小心谨慎,避免误用老版本的Java触发这种内存泄漏隐患。

当然现在一般都是用Java 8以上的版本,尤其较多的是用Java 9、Java 10甚至Java 11这几个新版本了,但是不排除有一些公司的非常老旧的系统在维护的时候,用的还是曾经很风靡的Java 6这个版本,大家在对这类老系统维护的时候,一定要谨慎注意substring内存泄漏问题

END

====惊喜====