一、概念:无用对象、未被引用对象、被引用对象
我们需要先了解一下对象在内存中的状态。下面的这张图就解释了什么是无用对象以及什么是未被引用对象。
上面图中可以看出,里面有被引用对象和未被引用对象。未被引用对象会被垃圾回收器回收,而被引用的对象却不会。未被引用的对象当然是不再被使用的对象,因为没有对象再引用它。然而无用对象却不全是未被引用对象。其中还有被引用的。就是这种情况导致了内存泄漏。
二、内存泄漏与内存溢出的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
一、释放对象的根本原则就是对象不会再被使用:
- 给对象赋予了空值null,之后再没有调用过。
- 另一个是给对象赋予了新值,这样重新分配了内存空间。
二、容易引起内存泄漏的几大原因
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。
来先看看下面的例子,为什么会发生内存泄漏。下面这个例子中,A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存问题,因为如果A引用更多这样的对象,那将有更多的未被引用对象存在,并消耗内存空间。
B对象也可能会持有许多其他的对象,那这些对象同样也不会被垃圾回收器回收。所有这些没在使用的对象将持续的消耗之前分配的内存空间。
①静态集合类
像HashMap、Vector 等静态集合类的使用最容易引起内存泄漏,因为这些静态变量的生命周期与应用程序一致,如示例1,如果该Vector 是静态的,那么它将一直存在,而其中所有的Object对象也不能被释放,因为它们也将一直被该Vector 引用着。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
这是网上最常见的一个例子,当定义了一个Object之后,o指向它,正常来说如果o指向null了,你们这个对象就不再被引用了直接被回收。但问题是这个对象被加入到一个容器之中了,这样就不仅仅是o指向它,容器也有指针指向它,所以回收器不会回收这个对象,而这个容器是static的,生命周期基本与jvm的相同,也就是说它持有的所有对象都不会被回收,所以如果想回收这个对象,一定要调用容器的remove方法。或者在容器不用之后直接向容器赋值未null
②监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有去删除这些监听器,从而增加了内存泄漏的机会。
③物理连接
一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭Connection ,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。
④变量不合理的作用域
如果一个变量定义的作用范围大于其实用范围,很可能导致内存泄漏
class Server{
private String msg;
public void receiveMsg()
{
readFromNet(); //从网络接收数据保存到msg中
saveDB(); //把msg保存到数据库中
}
}
这种解决方法:
①将msg作为局部变量,当方法结束,msg的生命周期结束,这样GC就可以回收了
②在使用完后将msg设置为null,这样GC也可以回收了