这里写目录标题

  • 什么是内存泄露
  • Java中内存泄露场景
  • 1、使用静态的集合类
  • 2 、单例模式可能会造成内存泄露
  • 3 、变量不合理的作用域
  • 4、数据库、网络、输入输出`流`,这些资源没有显示的关闭
  • 5、使用非静态内部类
  • 6、hashCode,equals重写错误


什么是内存泄露

一、什么是内存泄露,为什么会出现内存泄露

内存泄露是指不再使用的对象由于仍然被其他对象引用导致垃圾收集器不能及时释放这些对象占用的内存从而造成内存空间浪费的现象。从内存泄露的概念我们知道了之所以产生内存内漏,是因为长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用,会导致短生命周期对象不能被回收,举个例子更详细的说明一下。

Java中内存泄露场景

1、使用静态的集合类

静态的集合类的生命周期和应用程序的生命周期一样长,所以在程序结束前容器中的对象不能被释放,会造成内存泄露。解决办法是最好不使用静态的集合类,如果使用的话,在不需要容器时要将其赋值为null。

2 、单例模式可能会造成内存泄露

单例模式只允许应用程序存在一个实例对象,并且这个实例对象的生命周期和应用程序的生命周期一样长,如果单例对象中拥有另一个对象的引用的话,这个被引用的对象就不能被及时回收。解决办法是单例对象中持有的其他对象使用弱引用,弱引用对象在GC线程工作时,其占用的内存会被回收掉,如下示例:

public class SingleTon1 {    
    private static final SingleTon1 mInstance = null;    
    private WeakReference<Context> mContext;  
    private SingleTon1(WeakReference<Context> context) {    
    mContext = context;  
    }    
    
    public static SingleTon1 getInstance(WeakReference<Context> context) {    
        if (mInstance == null) {    
            synchronized (SingleTon1.class) {    
                if (mInstance == null) {    
                    mInstance = new SingleTon1(context);    
                }    
            }    
        }    
        return mInstance;    
    }    
}    
  
  
public class MyActivity extents Activity {  
    public void onCreate (Bundle savedInstanceState){  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.main);  
       SingleTon1 singleTon1 = SingleTon1.getInstance(new WeakReference<Context>(this));  
   }  
  
}

3 、变量不合理的作用域

如果变量的定义范围大于使用范围,并且在使用完后没有赋值为null的话,会出现内存泄露。定义变量的时候,能定义为局部变量就不要定义为成员变量,或者定义为成员变量的话,在使用完变量后,把变量赋值为null

4、数据库、网络、输入输出,这些资源没有显示的关闭

垃圾回收只负责内存回收,如果对象正在使用资源的话,Java虚拟机不能判断这些对象是不是正在进行操作,比如输入输出,也就不能回收这些对象占用的内存,所以在资源使用完后要调用close()方法关闭。

5、使用非静态内部类

非静态内部类对象的构建依赖于其外部类,内部类对象会持有外部类对象的this引用,即时外部类对象不再被使用了,其占用的内存可能不会被GC回收,因为内部类的生命周期可能比外部类的生命周期要长,从而造成外部类对象不能被及时回收。解决办法是尽量使用静态内部类,静态内部类只是形式上在外部类的里面,静态内部类不会持有外部类的引用,可以把静态内部类理解成是一个独立的类,和外部类没什么关系。

引申:为什么非静态内部类对象会持有外部类对象的this引用?

非静态内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,通过如下三个步骤,内部类对象通过this访问外部类对象的成员。

1) 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;

2) 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;

3)在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

6、hashCode,equals重写错误

为何要重写hashCode?
Java中的集合(Collection)有两类,一类是List,再有一类是Set。
前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 那么我们怎么判断两个元素是否重复呢? 这就是Object.equals方法了。

通常想查找一个集合中是否包含某个对象,就是逐一取出每个元素与要查找的元素进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则返回否定的信息,如果一个集合中有很多元素譬如成千上万的元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出成千上万个元素进行逐一比较才能得到结论,于是,有人就发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域

hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

为何会泄露
当一个对象被存储进hashset集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进hashset集合时的哈希值就不同了,这种情况下,即使在contains方法使用该对象的当前引用作为的参数去hashset集合中检索对象,也将返回找不到对象的结果,这也会导致无法从hashset集合中单独删除当前对象,从而造成内存泄露。

简单来说,如果重写错误的hashcode,导致放进去的对象无法被找到,无法执行删除该元素,积压多了,内存就不断增长