文章目录
  1. 1. 什么是内存泄漏
  2. 2. 为什么会发生内存泄漏?
  3. 3. 如何防止内存泄漏的发生?
  4. 4. 为什么JDK6中的substirng()方法容易导致内存泄漏?
    1. 4.1. substring()是做什么的?
    2. 4.2. 当substring()被调用的时候,内部发生什么事?
    3. 4.3. JDK6中的substring()
    4. 4.4. jdk6中substring()将会导致的问题
    5. 4.5. JDK7中的substring()

Java语言的一个关键的优势就是它的内存管理机制。你只管创建对象,Java的垃圾回收器帮你分配以及回收内存。然而,实际的情况并没有那么简单,因为内存泄漏在Java应用程序中还是时有发生的。文章内容来源于importnew。

什么是内存泄漏


内存泄漏的定义:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还在被引用着。

要想理解这个定义,我们需要先了解一下对象在内存中的状态。下面的这张图就解释了什么是无用对象以及什么是未被引用对象。
图1
上面图中可以看出,里面有被引用对象和未被引用对象。未被引用对象会被垃圾回收器回收,而被引用的对象却不会。未被引用的对象当然是不再被使用的对象,因为没有对象再引用它。然而无用对象却不全是未被引用对象。其中还有被引用的。就是这种情况导致了内存泄漏

为什么会发生内存泄漏?


来先看看下面的例子,为什么会发生内存泄漏。下面这个例子中,A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存问题,因为如果A引用更多这样的对象,那将有更多的未被引用对象存在,并消耗内存空间。

B对象也可能会持有许多其他的对象,那这些对象同样也不会被垃圾回收器回收。所有这些没在使用的对象将持续的消耗之前分配的内存空间。
图2

如何防止内存泄漏的发生?

下面是几条容易上手的建议,来帮助你防止内存泄漏的发生。

  • 特别注意一些像HashMap、ArrayList的集合对象,它们经常会引发内存泄漏。当它们被声明为static时,它们的生命周期就会和应用程序一样长。
  • 特别注意事件监听和回调函数。当一个监听器在使用的时候被注册,但不再使用之后却未被反注册。
  • “如果一个类自己管理内存,那开发人员就得小心内存泄漏问题了。” 通常一些成员变量引用其他对象,初始化的时候需要置空。

为什么JDK6中的substirng()方法容易导致内存泄漏?


substring()是做什么的?

substring(int beginIndex ,int endIndex)方法返回一个子字符串,返回的是从原字符串的beginIndex到endIndex-1之间的内容。例如:

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

输出

1
bc

当substring()被调用的时候,内部发生什么事?

你或许会认为由于x是不可变的对象,当x被x.substring(1,3)返回的结果赋值后,它将指向一个全新的字符串如下图:
图3
然而,这个图并不完全正确,或者说并没有完全表示出java 堆中真正发生的事情。那么当调用substring()的时候到底发生的了什么事呢?JDK 6与JDK7的substring方法实现有什么不一样呢?

JDK6中的substring()

java中字符串是通过字符数组来支持实现的,在JDK6中,String类包含3个域,char[] value、int offset、int count。分别用于存储真实的字符数组、数组的偏移量,以及String所包含的字符的个数。

当substring()方法被调用的时候,它会创建一个新的字符串对象,但是这个字符串的值在java 堆中仍然指向的是同一个数组,这两个字符串的不同在于他们的count和offset的值。
图4

下面是jdk6中的原代码,是简化后只包含用来说明这个问题的关键代码:

1
2
3
4
5
6
7
8
9
10
11
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}

jdk6中substring()将会导致的问题

如果你有一个非常长的字符串,但是你仅仅只需要这个字符串的一小部分,这就会导致性能问题(译注:可能会造成内存泄露,这个bug很早以前就有提及),因为你需要的只是很小的部分,而这个子字符串却要包含整个字符数组,在jdk6中解决办法就是使用下面的方法,它会指向一个真正的子字符串。

1
x = x.substring(x, y) + ""

JDK7中的substring()

在JDK7中有所改进,substring()方法在堆中真正的创建了一个新的数组,当原字符数组没有被引用后就被GC回收了.因此避免了上述问题.
图5

1
2
3
4
5
6
7
8
9
10
11
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}

public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}

文章目录
  1. 1. 什么是内存泄漏
  2. 2. 为什么会发生内存泄漏?
  3. 3. 如何防止内存泄漏的发生?
  4. 4. 为什么JDK6中的substirng()方法容易导致内存泄漏?
    1. 4.1. substring()是做什么的?
    2. 4.2. 当substring()被调用的时候,内部发生什么事?
    3. 4.3. JDK6中的substring()
    4. 4.4. jdk6中substring()将会导致的问题
    5. 4.5. JDK7中的substring()