HashMap内存泄露

背景

HashMap对于Java开发人员来说,应该是一种非常非常熟悉的数据结构了,应用场景相当广泛。
本文重点不在于介绍如何使用HashMap,而是关注在使用HashMap过程中,可能会导致内存泄露的情况,下面将以示例的形式展开具体介绍。

注意:理解本文的前提需要先熟悉HashMap原理。
为了更快的看到java.lang.OutOfMemoryError: Java heap space,我们可以配置下IDEA的JVM参数,简单配置下初始堆和最大堆参数为3M,-Xmx3m -Xms3m,如下图

场景一:重写hashcode、equals,put同一个对象,但是put前成员属性值发生了改变

直接上示例代码:

public class Test {
    public static void main(String[] args) {
        Map<Person, Integer> map = new HashMap<>();
        Person p = new Person("0", 10);

        for (int i = 0; i < 50000; i++) {
            p.setName(String.valueOf(i));
            map.put(p, 1);
            System.out.println(map.size());
        }

        System.out.println("end.");
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (obj instanceof Person) {
            Person personValue = (Person) obj;
            if (personValue.getName() == null && name == null) {
                return true;
            }
            return personValue.getName() != null && personValue.getName().equals(name);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

直接点击运行,查看结果,发现当put第49153个对象时,报了java.lang.OutOfMemoryError: Java heap space

49152
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.resize(HashMap.java:703)
	at java.util.HashMap.putVal(HashMap.java:662)
	at java.util.HashMap.put(HashMap.java:611)
	at app.Test.main(Test.java:23)

结果分析:本来,HashMap put同一个对象,理论上是会覆盖的,不会导致内存泄露,这里之所以出现这种情况,主要是因为我们put的并不是同一个对象(重写了hashcode和equals方法,且hashcode发生了改变),然后一直put,就导致对象越来越多,最终触发OutOfMemoryError。

场景二:没有重写hashcode、equals,put的对象每次都是new出来的

直接上示例代码:

public class Test {
    public static void main(String[] args) {
        Map<Person, Integer> map = new HashMap<>();
        for (int i = 0; i < 500000; i++) {
            map.put(new Person("0", 10), 1);
            System.out.println(map.size());
        }

        System.out.println("end.");
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

同样,直接点击运行,查看结果,发现也报了java.lang.OutOfMemoryError: Java heap space

39951Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.nio.CharBuffer.wrap(CharBuffer.java:373)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.newLine(PrintStream.java:545)
	at java.io.PrintStream.println(PrintStream.java:737)
	at app.Test.main(Test.java:21)

结果分析:这个没啥好说,Object默认的hashcode对于new出来的对象都是不同的,然后一直put,就导致对象越来越多,最终触发OutOfMemoryError。

总结

当使用HashMap执行put操作的时候,如果你期望的结果是覆盖这个key,那么你要再三确认put的时候,key对象的hashcode有没有发生变化,否则可能会有意想不到的结果;
建议,当想要使用对象作为HashMap的key时,可以考虑使用不可变对象作为HashMap的key,如常用的String类型,或者确保使用不可变的成员属性来生成hashcode;

posted @ 2020-06-08 16:23  风一样的码农  阅读(189)  评论(0编辑  收藏