当前位置: 首页 > 编程笔记 >

Java中由substring方法引发的内存泄漏详解

孟佑运
2023-03-14
本文向大家介绍Java中由substring方法引发的内存泄漏详解,包括了Java中由substring方法引发的内存泄漏详解的使用技巧和注意事项,需要的朋友参考一下

内存溢出(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);

上述程序的输出是“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(int offset, int count, char value[]) {
 this.value = value;
 this.offset = offset;
 this.count = count;
 }
String str = "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是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:

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

JDK7中的substring实现

在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。

查看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);
 }
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);
 }

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;
 }

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

总结

以上就是本文关于Java中由substring方法引发的内存泄漏详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

 类似资料:
  • 问题内容: 据说 String类中的方法会导致内存泄漏。是真的吗 怎么样?有什么替代方法吗? 尤其是在寻找答案时, 还有哪些其他原因可以导致Java的内存泄漏呢?这将帮助我在编码时注意。 问题答案: 在过去的JDK版本中,该方法的实现将建立一个新对象,该对象保留对整个char数组的引用,以避免复制它。因此,您可能会无意中仅使用一个字符串来引用一个很大的字符数组。 现在,此方法已更改,并且此“泄漏”

  • 本文向大家介绍Java 内存泄漏,包括了Java 内存泄漏的使用技巧和注意事项,需要的朋友参考一下 在Java中,垃圾回收(析构函数的工作)是使用垃圾回收自动完成的。但是,如果代码中有引用它们的对象怎么办?它无法取消分配,即无法清除其内存。如果这种情况一再发生,并且创建或引用的对象根本没有被使用,它们就会变得无用。这就是所谓的内存泄漏。 如果超过了内存限制,则程序将通过抛出错误(即“ OutOfM

  • 问题内容: 我正在遍历String类API,由于子字符串方法与原始String共享相同的字符数组,因此似乎存在潜在的内存泄漏。 如果原始字符串很大,则子字符串返回的小字符串可以防止原始字符串(由大数组备份)被Java垃圾回收。 任何想法或我读错了API。 问题答案: 还有 就是 对内存泄漏潜在的,如果你把一个相当大的字符串的一个子并不能复印(通常是通过构造函数)。 请注意,自Java 7u6以来,

  • 在MAT工具中分析了堆转储后。泄漏嫌疑人说:“”加载的一个“java.util.TaskQueue”实例占用680,207,896(82.39%)字节。该实例由org.apache.tomcat.util.threads.taskThread@0xC1B52018 ajp-bio-8009-exec-243引用,由“java.net.urlClassLoader@0xCE67A9B8”加载。内存积

  • 问题内容: 我发现使用是众所周知的与相关的内存问题。 使用中是否存在内存泄漏? 如果是,解决方法是什么? 以下链接显示了Java中子字符串的正确用法。 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4513622 另外一个博客谈论子字符串中可能的MLK。 http://nflath.com/2009/07/the-dangers-of- st

  • 本文向大家介绍浅析Java中的内存泄漏,包括了浅析Java中的内存泄漏的使用技巧和注意事项,需要的朋友参考一下 ava最明显的一个优势就是它的内存管理机制。你只需简单创建对象,java的垃圾回收机制负责分配和释放内存。然而情况并不像想像的那么简单,因为在Java应用中经常发生内存泄漏。 本教程演示了什么是内存泄漏,为什么会发生内存泄漏以及如何预防内存泄漏。 什么是内存泄漏? 定义:如果对象在应用中