欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。

在Java中我们无须关心内存的释放,JVM提供了内存管理机制,有垃圾回收器帮助回收不需要的对象。但实际中一些不当的使用仍然会导致一系列的内存问题,常见的就是内存泄漏和内存溢出

内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出。

内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。

由substring方法引发的内存泄漏

substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全不同的(虽然它们都达到了同样的效果)。了解它们实现细节上的差异,能够更好的帮助你使用它们,因为在JDK1.6中不当使用substring会导致严重的内存泄漏问题。

1、substring的作用

substring(int beginIndex, int endIndex)方法返回一个子字符串,从父字符串的beginIndex开始,结束于endindex-1。父字符串的下标从0开始,子字符串包含beginIndex而不包含endIndex。

String x= "abcdef";
x= str.substring(1,3);
System.out.println(x);"abcdef";
x= str.substring(1,3);
System.out.println(x);

上述程序的输出是“bc”

2、实现原理

String类是不可变变,当上述第二句中x被重新赋值的时候,它会指向一个新的字符串对象,就像下面的这幅图所示:

然而,这幅图并没有准确说明的或者代表堆中发生的实际情况,当substring被调用的时候真正发生的才是这两者的差别。

JDK6中的substring实现

String对象被当作一个char数组来存储,在String类中有3个域:char[] value、int offset、int count,分别用来存储真实的字符数组,数组的起始位置,String的字符数。由这3个变量就可以决定一个字符串。当substring方法被调用的时候,它会创建一个新的字符串,但是上述的char数组value仍然会使用原来父数组的那个value。父数组和子数组的唯一差别就是count和offset的值不一样,下面这张图可以很形象的说明上述过程。

看一下JDK6中substring的实现源码:

public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > count) {throw new StringIndexOutOfBoundsException(endIndex);}if (beginIndex > endIndex) {throw new StringIndexOutOfBoundsException(endIndex - beginIndex);}return ((beginIndex == 0) && (endIndex == count)) ? this :new String(offset + beginIndex, endIndex - beginIndex, value); //使用的是和父字符串同一个char数组value} String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > count) {throw new StringIndexOutOfBoundsException(endIndex);}if (beginIndex > endIndex) {throw new StringIndexOutOfBoundsException(endIndex - beginIndex);}return ((beginIndex == 0) && (endIndex == count)) ? this :new String(offset + beginIndex, endIndex - beginIndex, value); //使用的是和父字符串同一个char数组value}
String(int offset, int count, char value[]) {this.value = value;this.offset = offset;this.count = count;}set, int count, char value[]) {this.value = value;this.offset = offset;this.count = count;}

由此引发的内存泄漏泄漏情况:

String str = "abcdefghijklmnopqrst";
String sub = str.substring(1, 3);
str = null;"abcdefghijklmnopqrst";
String sub = str.substring(1, 3);
str = null;

这段简单的程序有两个字符串变量str、sub。sub字符串是由父字符串str截取得到的,假如上述这段程序在JDK1.6中运行,我们知道数组的内存空间分配是在堆上进行的,那么sub和str的内部char数组value是公用了同一个,也就是上述有字符a~字符t组成的char数组,str和sub唯一的差别就是在数组中其实beginIndex和字符长度count的不同。在第三句,我们使str引用为空,本意是释放str占用的空间,但是这个时候,GC是无法回收这个大的char数组的,因为还在被sub字符串内部引用着,虽然sub只截取这个大数组的一小部分。当str是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:

String str = "abcdefghijklmnopqrst";
String sub = str.substring(1, 3) + "";
str = null;"abcdefghijklmnopqrst";
String sub = str.substring(1, 3) + "";
str = null;

利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在JDK7中,substring 被重新实现了。

JDK7中的substring实现

在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。下面的这张图说明了JDK7中substring的实现过程:

查看JDK7中String类的substring方法的实现源码:

public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) {throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);} String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) {throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);}
public String(char value[], int offset, int count) {if (offset < 0) {throw new StringIndexOutOfBoundsException(offset);}if (count < 0) {throw new StringIndexOutOfBoundsException(count);}// Note: offset or count might be near -1>>>1.if (offset > value.length - count) {throw new StringIndexOutOfBoundsException(offset + count);}this.value = Arrays.copyOfRange(value, offset, offset+count);} String(char value[], int offset, int count) {if (offset < 0) {throw new StringIndexOutOfBoundsException(offset);}if (count < 0) {throw new StringIndexOutOfBoundsException(count);}// Note: offset or count might be near -1>>>1.if (offset > value.length - count) {throw new StringIndexOutOfBoundsException(offset + count);}this.value = Arrays.copyOfRange(value, offset, offset+count);}

Arrays类的copyOfRange方法:

public static char[] copyOfRange(char[] original, int from, int to) {int newLength = to - from;if (newLength < 0)throw new IllegalArgumentException(from + " > " + to);char[] copy = new char[newLength];   //是创建了一个新的char数组System.arraycopy(original, from, copy, 0,Math.min(original.length - from, newLength));return copy;} static char[] copyOfRange(char[] original, int from, int to) {int newLength = to - from;if (newLength < 0)throw new IllegalArgumentException(from + " > " + to);char[] copy = new char[newLength];   //是创建了一个新的char数组System.arraycopy(original, from, copy, 0,Math.min(original.length - from, newLength));return copy;}

可以发现是去为子字符串创建了一个新的char数组去存储子字符串中的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。

欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。

Java中由substring方法引发的内存泄漏相关推荐

  1. java中的hashcode方法作用以及内存泄漏问题

    本文装载:http://hi.baidu.com/iduany/item/6d66dfc9d5f2da1650505870 hashCode()方法的作用&使用分析 一直以来都想写篇文章来说明 ...

  2. java substr函数_oracle 中的substr()函数的用法,以及与java中String.substring()方法的区别...

    oracle 中的substr()函数的规则是 substr( string, start_position, [ length ] ) 说明: string 是要分割的字符串 start_posit ...

  3. Java中的substring()方法

    目录 一.public String substring(int beginIndex) 方法 二.public String substring(int beginIndex, int endInd ...

  4. java string.substring 参数,Java,String类中的subString()方法,stringsubstring

    Java,String类中的subString()方法,stringsubstring public class TestStringSubString { // main函数 public stat ...

  5. 【Java】字符串substring方法在jkd6,7,8中的差异

    1.概述 转载:注意:字符串substring方法在jkd6,7,8中的差异 这篇文章讲的更好:灵魂拷问:Java 的 substring() 是如何工作的? 标题中的substring方法指的是字符 ...

  6. java list sublist方法_(转)Java 中 List.subList() 方法的使用陷阱

    原文:http://blog.csdn.net/cleverGump/article/details/51105235 前言 本文原先发表在我的 iteye博客: http://clevergump. ...

  7. java中的dispose()方法

    java中的dispose()方法 先来看看 JAVA 1.5 的帮助文档的原文 - dispose - public void dispose() ****** 释放由此 Window.其子组件及其 ...

  8. java 死锁 内存消耗_详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并 ...

  9. Java中重写equals()方法时注意点

    Java中重写equals()方法时注意点 一直说,重写一个对象的equals()方法时我们必须重写HashCode()方法,但是如果我们不重写呢?会有什么影响呢? 首先看一下,什么情况下我们需要重写 ...

最新文章

  1. 一文了解5G是什么,将如何影响我们的未来
  2. Yii直接加载JS/CSS
  3. filebeat相关registry文件内容解析
  4. 构造函数还是静态工厂方法?
  5. 互站卖的分发美化版可以封装双端APP
  6. KingDZ 变菜鸟,每日一个C#小实例之---玩转鼠标
  7. 冲刺第一天 11.23 FRI
  8. 光伏发电与计算机控制,独立太阳能光伏发电系统的控制设计与实现
  9. 跨平台显示MMD模型
  10. Java 求解加油站
  11. linux修改mac地址_如何(以及为什么)在Windows,Linux和Mac上更改您的MAC地址
  12. LWR 局部加权线性回归算法
  13. mysql附加数据库
  14. 安装 macbook 双系统( OS X 和 Ubuntu )
  15. Add Python Interpreter 报错 Error code:2. XX can‘t open file XX [Errno 2] No such file or directory
  16. 印度电线标准IS 694(R2020),印度插头标准IS 1293(R2020)
  17. ACM2019春季训练- How Many Tables HDU - 1213(初识并查集+转倚天屠龙记故事)
  18. 群体遗传学---admixture软件快速群体分群
  19. B站,竟然成了一个求偶平台。
  20. 【题解】CF1550F Jumping Around

热门文章

  1. 计算机网络按信号频带占用方式,[大学计算机应用基础第六章计算机网络基础.ppt...
  2. brother标签打印软件_标签打印软件如何设计食品留样标签模板
  3. MAC下 IEDA发布tomcat项目的位置
  4. FortiGate设置E-mail告警
  5. ASPxGridView EditFormLayout修改 TextBox文本长度
  6. Placeholder in IE8 and older
  7. (原)数据结构——线索二叉树
  8. 基于Socket的java网络编程
  9. 定制属于你的HouseMD
  10. 《教师教学究竟靠什么--谈新课程的教学观》之交往与互动的教学观 心得体会三...