一、什么是内存泄漏?

内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成系统的浪费,导致程序运行速度减慢甚至系统崩溃等严总后果。

内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更加难以检测。

二、JAVA中是否存在内存泄漏?什么情况下才是内存泄漏?

存在。因为JAVA语言中有着GC(垃圾回收器)机制的存在,一般情况下是很少发生内存泄漏。但是当长生命周期的对象持有短生命周期的对象的引用就有可能发生内存泄漏。尽管短生命周期的对象已经不需要,但是因为长生命周期对象持有它的引用而不能被回收,从而发生了内存泄漏。

内存泄漏的分类:

1.常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。

2.偶发性内存性泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许会变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

3.一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

4.隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。

内存泄漏主要发生在以下几种情况:

一.静态的集合类

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

二.集合中属性的对象值被修改时

public static void main(String[] args)

{

    Set<Student> set = new HashSet<Student>();

    Student s1 = new Student("Jack");

    Student s2 = new Student("Mary");

    Student s3 = new Student("Eason");

 

    set.add(s1);

    set.add(s2);

    set.add(s3);

 

    System.out.println(set.size());//3

    s2.setName("Jackson"); //修改属性,此时s2元素对应的hashcode值发生改变

    set.remove(s2);        // remove不掉,造成内存泄漏

    set.add(s2);           // 添加成功

 

    System.out.println(set.size());//4

}

在上述例子中,s2的属性值被修改,因此不能在set中删除,所以set中会一致保持着s2的引用,不能被回收,因此发生了内存泄漏。

三.监听器

在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们增加监听器以后,在释放对象的时候却没有去删除这些监听器,从而增加了内存泄漏的机会。

四.各种连接

比如数据库连接,网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

五.外部模块的引用

内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

六.单例模式