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