内存泄露是指一个不再被程序使用的对象或变量还在内存中占有存储空间。
在C/C++中,内存分配与释放是由开发人员来负责的,如果开发者忘记释放已经分配的内存就会造成内存泄露。但是在java中,内存的回收是通过垃圾回收器(GC)来实现的,那么在java中出现内存泄露问题是怎么发生的呢?
通过内存泄露的定义,我们不难得出内存泄露的两种情况:
- 在堆中申请的空间没有被释放;
2.对象已不在被使用,但是还在内存中保留着。
首先,判断一个内存空间是否符合垃圾回收的标准:一、对象的值是空值null,以后在没有被使用过。二、给对象赋予一个新值,重新分配了内存空间。通过对GC回收标准的分析,我们可以知道,在java中出现内存泄露的情况一般都是第二种情况。如果这里还有疑问的么可以参照我前面的博客GC简介。下面我们对其进行一个全面的分析。
实现给出一个实例来了解Java中内存泄露:
Vector v = new Vector();
for(int i=0;i<10;i++){
Object o = new Object();
v.add(o);
}
看起来好像没什么问题,好像我们平时也会这么写对吧。但是实际上,在上面例子的循环中,不断常见新的对象加到Veactor对象中,当退出循环后,o的作用域将会结束,但是由于v在使用这么对象,因此垃圾回收器无法对其回收,此时就造成了内存泄露。只有将这些对象从Vector中删除才能释放创建的对象。
在java中,能引用内存泄露的原因很多,主要体现在以下的几个方面:
1) 静态集合类,例如HashMap和Vector。如果这些容器为静态的。由于他们的生命周期与程序一致,那么容器中对祥在程序结束之前将不能被释放,从而造成内存泄露。如上例所示。
2)各种连接没有及时断开,例如数据库链接、网络连接以及IO连接等。在对数据库进行操作时,首先需要建立与数据库的连接,当不在使用时,需调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才能回收相对的对象,否则,如果在访问数据库的过程中,对Connection、Statement不显示的关闭,将会造成大量的对象无法被回收。
3) 监听器。在Java语言中,往往会使用到监听器。通常一个应用会用到多个监听器,但在释放对象的同时往往没有相对应的删除监听器,这也可能导致内存泄露。
4)变量不合理的作用域。例如下面的实例:
class Servce{
private String msg;
public void recieveMsg(){
//从网络接受数据保存到msg中
//把msg保存到数据库中
}
}
在上述代码中,在revieveMsg()方法将接受的消息保存在变量msg中,并保存在数据中中。保存到数据中之后,msg已经没有用了, 但是由于Msg的生命周期与对象的生命周期一样长,此时msg还不能被回收,因此会造成内存泄露。对于这个问题,有两种解决方案,一种方式时将Msg定义在recieveMsg()方法下,另一种方式时在Msg使用完之后,将msg设置为null。这样垃圾回收器也会自动回收msg内容所占的内存空间了。
5)不适当的单利模式。
class BigClass{
//***
}
class Sington{
private BigClass big;
private Sington(BigClass big){this/big = big;}
private static Singleton instale = new Sington(new BigClass());
public Singleton getInstance(){
return instance;
}
}
在上述的单利模式中,sington存在一个对对象BigClass的引用,由于单利对象以静态变量的方式存储,因此他在JVM的整个生命周期中都存在,同时由于他有一个对对象BigClass的引用,这样会导致BigClass类的对象不能够被回收。