zoukankan      html  css  js  c++  java
  • 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;

  • 相关阅读:
    程序员的自我修养 符号修饰 函数签名 以及一个引申的问题: extern "c"
    Spring.NET学习笔记(1)基本依赖注入
    Spring.NET学习笔记(3)注册事件注入
    Spring.NET学习笔记(2)依赖注入细节
    jQuery LigerUI 使用教程入门篇
    Spring.NET学习笔记(5)对象生命周期和创建者对象
    【C#.NET】C#皮肤与主题应用实例
    【VB/C#】Kill进程
    【C#.NET】C#用户控件的使用
    【C#.NET】C#创建多语言网站
  • 原文地址:https://www.cnblogs.com/chenpi/p/13066436.html
Copyright © 2011-2022 走看看