背景
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;