Java内存泄漏发生的情况

在Java 语言中,内存在以下情况下被回收:

1、没有任何引用指向它。

2、GC被运行。

而判断一块内存空间是否符合垃圾收集器收集标准,即没有任何引用指向它的情况有三种:

1、new了对象但是从未引用过,那么这个对象马上就会被回收。

2、给对象赋予了空值null,以下再没有调用过。

3、给对象赋予了新值,即重新分配了内存空间。

如果一块没有用的内存没有把它赋予上面2、3两种状态,那么就会造成内存泄露(无用的应该被回收的内存没有被回收)。实际编程中造成内存泄露的情况主要有以下几种:

静态集合类

在使用Set、Vector、HashMap等集合类的时候需要特别注意,有可能会发生内存泄漏。当这些集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长,这时候,就有可能会发生内存泄漏

1
2
3
4
5
6
7
8
9
10
class StaticTest{
private static Vector v = new Vector(10);
public void init(){
for (int i = 1; i < 100; i++){
Object object = new Object();
v.add(object);
object = null;
}
}
}

在上面的代码中,循环申请了Object对象,并添加到Vector中,然后将对象设置为null,可是这些对象因为被Vector引用着,因此并不能被GC回收,因此造成了内存泄漏。因此,要释放这些对象,还需要被它们从Vector删除,最简单的方法就是将Vector设置为null。

集合里的对象属性值被改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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中,我们经常会使用到监听器,如对某个控件添加单击监听器addOnClickListener(),但往往释放对象的时候会忘记删除监听器,这就有可能造成内存泄漏。好的方法就是,在释放对象的时候,应该记住释放所有监听器,这就能避免了因为监听器而导致的内存泄漏。
在Android中使用setOnClickListener(null)来实现内存回收。

各种连接

Java中的连接包括数据库连接、网络连接和io连接,如果没有显式调用其close()方法,是不会自动关闭的,这些连接就不能被GC回收而导致内存泄漏。一般情况下,在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。

外部模块的引用

调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:
public void register(Object o)
这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()。这种情况容易忽略,而且发生了内存泄漏的话,比较难察觉,应该在编写代码过程中就应该注意此类问题。

单例模式

使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重,因此需要特别注意此类情况。这种情况就需要考虑下单例模式的设计会不会有问题,应该怎样保证不会产生内存泄漏问题。