当前位置: 首页 > 科技观察

接过前同事写的烂Java代码,一不小心造成了内存泄露

时间:2023-03-16 15:26:38 科技观察

今天给大家讲讲最常见的String字符串代码,它的一些底层原理,以及内存的使用问题不当可能导致的泄漏,相信对大家日常开发和写代码会有帮助。String字符串在内存中是如何存储的?首先,我们平时在代码中写一行String类型的代码的时候,你知道这个String字符串在内存中是怎么存储的吗?比如这样一行代码:Stringusername="zhangsan",这个"zhangsan"其实就是一串串字符串,实际上是存储在最底层的一个数组中,数组的大小严格等于字符串的长度,He是不可变的。如下图所示:接下来,对于Java中的字符串,有一个常量池的概念,就是对于相同的字符串内容,往往会在内存中用同一个数组来表示,而不是相同的字符串内容创建不同的用于存储的数组。例如我们来看下面两行代码:Stringusername="zhangsan";Stringnickname="张三";上面的用户名和昵称都指向“zhangsan”,其实是存储在同一个数组中的底层。如下图所示:所以,正是因为同一个字符串引用了同一个底层数组,所以如果使用类似于System.out.println(username==nickname)的判断代码,会发现username==nickname返回true,因为它们都指向同一个底层数组。另外给大家科普一下字符串的知识,就是如果我们用一个字符串创建一个String对象,那么它一定是内存中的另一个对象。如下代码所示,请看一看:Stringusername="zhangsan";Stringnickname=newString("张三");System.out.println(用户名==昵称);请看上面的代码,此时比较用户名和昵称还是返回true?那不可能,这时候肯定是false,因为此时在内存中,username指向的是一个数组,而nickname指向的是一个String对象,只不过这个String对象中有一个“zhangsan”字符串。如下图所示:不过这个时候我再给大家介绍一个知识点,就是String对象里面的“zhangsan”字符串是怎么存储的呢?其实String对象内部的“zhangsan”字符串还是引用了之前的数组,如下图所示:String.intern()方法那么,如果此时使用String.intern()方法,它我发现可以在String对象中获取“zhangsan”字符串,然后用这个字符串进行比较,还是返回true。看下面的代码就可以理解:Stringusername="zhangsan";Stringnickname=newString("zhangsan");System.out.println(username==nickname.intern());//返回一个trueString字符串怎么会导致内存泄漏呢?好了,那么大家了解了Java中字符串的基本原理,接下来我们就可以告诉大家我们平时写代码都是怎么使用字符串的,一不注意就会造成内存泄漏。该问题主要出现在Java6及之前的版本中。在这个旧的Java版本中,String.substring()的字符串拦截动作会导致内存泄漏。这是什么意思?让我们来看看。.在Java6之前的版本中,当你调用String.substring()进行字符串截取时,它在底层的运行方式是这样的,它会直接复制你原来字符串的数组,然后用一个偏移指针和计数标记来看截取表名后需要什么字符串。如下图所示:但是这种运行方式有个问题,就是你每次子串的时候都会复制一份原数组,但是对于你的子串,你只需要一部分,而你缺少每次都复制原始字符串,造成子串中不必要的复制内容的浪费。如下图,红色圈出的部分是子串不需要的:所以此时,红色圈出的部分子串不需要的内容仍然占用内存。这是什么问题?是典型的内存泄漏,也就是说,如果进行大量的substring等操作,可能会复制大量的字符串数组,那么很多复制的字符串数组中的很多内容都是不需要的,这样一来,它也占用了大量的内存空间,这就叫做内存泄漏。内存泄漏就是你的内存空间被占用了很多,但是你不去用,别人也用不着。这是典型的占厕所不拉屎的行为。所以后来在Java7版本中,重构了String.substring()的源码,开始改造这部分的实现。每次执行String.substring时,它会将原字符串数组中你需要的部分复制过来即可,避免每次都重复复制原字符串数组。如下图所示:综上所述,在本次Java7及之后的新版本中,完全解决了substring导致的内存泄露问题,所以大家在使用strings进行开发时一定要小心,避免误用旧版本的Java引发此问题内存泄漏危险。当然现在普遍使用Java8以上的版本,尤其是Java9、Java10甚至Java11等新版本用得更多。但是,不排除一些公司在维护非常老旧的系统时,仍然使用曾经流行的Java6版本。在维护此类旧系统时,必须特别注意子串内存泄漏。