Java中的内存泄露,广义并通俗的说,就是:再也不会被使用的对象的内存不能被回收,就是内存泄露。程序员
Java中的内存泄露与C++中的表现有所不一样。在C++中,全部被分配了内存的对象,再也不使用后,都必须程序员手动的释放他们。因此,每一个类,都会含有一个析构函数,做用就是完成清理工做,若是咱们忘记了某些对象的释放,就会形成内存泄露。编程
可是在Java中,咱们不用(也没办法)本身释放内存,无用的对象由GC自动清理,这也极大的简化了咱们的编程工做。但,实际有时候一些再也不会被使用的对象,在GC看来不能被释放,就会形成内存泄露。咱们知道,对象都是有生命周期的,有的长,有的短,若是长生命周期的对象持有短生命周期的引用,就极可能会出现内存泄露。咱们举一个简单的例子:数组
public class Simple { Object object; public void method1(){ object = new Object(); //...其余代码 } }
这里的object实例,其实咱们指望它只做用于method1()方法中,且其余地方不会再用到它,可是,当method1()方法执行完成后,object对象所分配的内存不会立刻被认为是能够被释放的对象,只有在Simple类建立的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object做为method1()方法中的局部变量。固然,若是必定要这么写,能够改成这样:函数
public class Simple { Object object; public void method1(){ object = new Object(); //...其余代码 object = null; } }
substring(int beginIndex, int endndex )是String类的一个方法,可是这个方法在JDK6和JDK7中的实现是彻底不一样的(虽然它们都达到了一样的效果)。在JDK1.6中不当使用substring会致使严重的内存泄漏问题。性能
substring(int beginIndex, int endIndex)方法返回一个子字符串,从父字符串的beginIndex开始,结束于endindex-1。父字符串的下标从0开始,子字符串包含beginIndex而不包含endIndex。this
String x = "abcdef"; x = x.substring(1,3); System.out.println(x); //output: bc
String类是不可变变,当上述第二句中x被从新赋值的时候,它会指向一个新的字符串对象。code
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 } public 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;
假如上述这段程序在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;
利用的就是字符串的拼接技术,它会建立一个新的字符串,这个新的字符串会使用一个新的内部char数组存储本身实际须要的字符,这样父数组的char数组就不会被其余引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。可是这样书写很明显是很差看的,因此在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方法,为子字符串建立了一个新的char数组去存储子字符串中的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...对vector的操做 //...与vector无关的其余操做 }
这里内存泄露指的是在对vector操做完成以后,执行下面与vector无关的代码时,若是发生了GC操做,这一系列的object是无法被回收的,而此处的内存泄露多是短暂的,容器是方法内的局部变量,形成的内存泄漏影响可能不算很大,由于在整个method()方法执行完成后,那些对象仍是能够被回收。这里要解决很简单,手动赋值为null便可:
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...对vector的操做 vector = null; // 手动赋值为null //...与vector无关的其余操做 }
若是这个容器做为一个类的成员变量,甚至是一个静态(static)的成员变量时,就要更加注意内存泄露了。
单例模式,不少时候咱们能够把它的生命周期与整个程序的生命周期看作差很少的,因此是一个长生命周期的对象。若是这个对象持有其余对象的引用,也很容易发生内存泄露。