内存泄漏介绍

程序在申请内存后,无用内存无法释放已申请的内存空间。

长生命周期的对象持有短生命周期对象的引用

例如:将ArrayList设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏

连接未关闭

如数据库连接、网络连接和IO连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。

这些未释放的网络连接、IO连接等,也会给数据库、系统带来负担

变量作用域不合理

1.一个变量的定义的作用范围大于其使用范围

在局部方法中定义大于这个方法作用域范围的变量

2.如果没有及时地把对象设置为null

例如:ThreadLocal中的值没有及时remove

内部类持有外部类

Java的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏

如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引用,导致垃圾回收器不能正常工作)

解决方法:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部类是否被回收;

Hash值改变

在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露。

代码示例

public class Node {

    private int x;
    private int y;

    public Node(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "Node{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }

    /**
     * 重写HashCode的方法,演示基于hash的容器内存泄露
     *
     * @return 对象的hashCode
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }

    /**
     * 改变y的值:同时改变hashcode
     *
     * @param y 新的y值
     */
    public void setY(int y) {
        this.y = y;
    }

    public static void main(String[] args) {
        HashSet<Node> hashSet = new HashSet<Node>();
        Node nod1 = new Node(1, 3);
        Node nod2 = new Node(3, 5);
        hashSet.add(nod1);
        hashSet.add(nod2);
        //nod2的Hash值改变
        nod2.setY(7);
        //删掉nod2节点
        hashSet.remove(nod2);
//        删除nod1节点
        hashSet.remove(nod1);
//        此时你感觉应该大小为0。但是大小为1。因为修改了对象的hashCode值导致内存泄露和程序逻辑错误
        System.out.println(String.format("hashSet大小:%s hashSet值:%s", hashSet.size(), hashSet.toString()));
    }

}