java内存泄漏的几种情景

 

1、Java内存回收机制 
不论哪一种语言的内存分配方式,都须要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法建立的,这些对象的建立都是在堆(Heap)中分配的,全部对象的回收都是由Java虚拟机经过垃圾回收机制完成的。GC为了可以正确释放对象,会监控每一个对象的运行情况,对他们的申请、引用、被引用、赋值等情况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否能够达到,若是不可到达,则就将其回收,这样也能够消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,如下再没有调用过,另外一个是给对象赋予了新值,这样从新分配了内存空间。
2、Java内存泄露引发缘由 
首先,什么是内存泄露?常常听人谈起内存泄露,但要问什么是内存泄露,没几个说得清楚。内存泄露是指无用对象(再也不使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而形成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
那么,Java内存泄露根本缘由是什么呢?长生命周期的对象持有短生命周期对象的引用就极可能发生内存泄露,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是java中内存泄露的发生场景。具体主要有以下几大类: 
一、静态集合类引发内存泄露: 
像HashMap、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 对象,并将所申请的对象放入一个Vector 中,若是仅仅释放引用自己(o=null),那么Vector 仍然引用该对象,因此这个对象对GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。
二、当集合里面的对象属性被修改后,再调用remove()方法时不起做用。
例: 
public static void main(String[] args) 
{ 
Set<Person> set = new HashSet<Person>(); 
Person p1 = new Person("唐僧","pwd1",25); 
Person p2 = new Person("孙悟空","pwd2",26); 
Person p3 = new Person("猪八戒","pwd3",27); 
set.add(p1); 
set.add(p2); 
set.add(p3); 
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! 
p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 

set.remove(p3); //此时remove不掉,形成内存泄漏
set.add(p3); //从新添加,竟然添加成功 
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! 
for (Person person : set) 
{ 
System.out.println(person); 
} 
}
三、监听器 
在java 编程中,咱们都须要和监听器打交道,一般一个应用当中会用到不少监听器,咱们会调用一个控件的诸如addXXXListener()等方法来增长监听器,但每每在释放对象的时候却没有记住去删除这些监听器,从而增长了内存泄漏的机会。
四、各类链接 
好比数据库链接(dataSourse.getConnection()),网络链接(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 是否提供相应的操做去除引用。
六、单例模式 
不正确使用单例模式是引发内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),若是单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,致使内存泄露,考虑下面的例子: 
class A{ 
public A(){ 
B.getInstance().setA(this); 
} 
.... 
} 
//B类采用单例模式 
class B{ 
private A a; 
private static B instance=new B(); 
public B(){} 
public static B getInstance(){ 
return instance; 
} 
public void setA(A a){ 
this.a=a; 
} 
//getter... 
} 
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下若是A是个比较复杂的对象或者集合类型会发生什么状况