Java的内存泄漏——HashSet和hashCode

  今天公司的L&L,同事介绍了Java的Reflection机制。最后提到了覆盖object对象的hashCode方法可能会导致内存泄漏,这里结合网上的其他资料来简单总结一下。

  首先,假设我们定义了一个ReflectPoint类,定义两个成员变量记录点的x和y坐标。object类的equals方法用来比较两个对象的内存地址是否相等(与==运算符等价),而我们此时希望两个实例对象,如果x和y分别相等,则调用equals方法后返回true。此时我们需要覆盖object类的equals方法:

@Override
public booleanequals(Object obj) {

    if (this == obj)
       return true;

    if (obj == null)
        return false;

    if (getClass() != obj.getClass())
        return false;

   final ReflectPoint other = (ReflectPoint) obj;

   if(x != other.x)
      return false;

   if(y != other.y)
      return false;

   return true;
}

  之后,我们覆盖object类的hashCode方法,使得x和y分别相等的点的散列值也一样(以下代码假设x和y都不超过30):

@Override
public inthashCode() {
   final intprime = 31;
   int result = 1;
   result = prime * result + x;
   result = prime * result + y;

   return result;
}

  此时,假如我们定义三个ReflectPoint类的实例,然后把他们全部添加到一个HashSet中:

  Collection collections=new HashSet();

  ReflectPoint pt1=new ReflectPoint(3,3);
  ReflectPoint pt2=new ReflectPoint(5,5);
  ReflectPoint pt3=new ReflectPoint(3,3);

  collections.add(pt1);
  collections.add(pt2);
  collections.add(pt3);
  collections.add(pt1);

  此时,该集合中的元素数量显然是2,因为pt1、pt3和后来再添加的pt1的hashCode相同,而HashSet不允许有散列值相同的元素插入。

  那么,加入我们随后修改了pt1的值,比如:

  pt1.y = 7;   //引起内存泄漏的地方

  collections.remove(pt1);

  这样,pt1就Remove不掉了,因为hashCode已经改变,在调用Remove方法时,计算得到的新散列值在集合中找不到,就会导致修改前的pt1对象仍然存在于集合中。

  总的来说,当一个对象被存储进hashSet集合以后,就不能再修改对象中的那些参与计算哈希算法的那些字段,否则会造成内存泄露。表面上程序代码在不断增加对象,删除对象,但实际内存中并没有删除,该对象以后不再用了,可是内存中却一直存在,造成浪费,最终导致内存泄露。


  附录1:HashMap实现代码(部分)

public V put(K key, V value) {   

        if (key == null)   
            return putForNullKey(value);   

        int hash = hash(key.hashCode());   

        int i = indexFor(hash, table.length);   

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {   
            Object k;   

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   
                V oldValue = e.value;   
                e.value = value;   
                e.recordAccess(this);   

                return oldValue;   

            }   

        }   

  

        modCount++;   
        addEntry(hash, key, value, i);   

        return null;   

    }  

  附录2:HashSet实现代码(部分)

public class HashSet<E>   
 extends AbstractSet<E>   
 implements Set<E>, Cloneable, java.io.Serializable   
{   

 // 使用 HashMap 的 key 保存 HashSet 中所有元素  
 private transient HashMap<E,Object> map;   

 // 定义一个虚拟的 Object 对象作为 HashMap 的 value   
 private static final Object PRESENT = new Object();   

 ...   

 // 初始化 HashSet,底层会初始化一个 HashMap   
 public HashSet()   
 {   
     map = new HashMap<E,Object>();   
 }   

 // 以指定的 initialCapacity、loadFactor 创建 HashSet   
 // 其实就是以相应的参数创建 HashMap   
 public HashSet(int initialCapacity, float loadFactor)   

 {   
     map = new HashMap<E,Object>(initialCapacity, loadFactor);   
 }   

 public HashSet(int initialCapacity)   
 {   
     map = new HashMap<E,Object>(initialCapacity);   
 }   
 HashSet(int initialCapacity, float loadFactor, boolean dummy)   
 {   
     map = new LinkedHashMap<E,Object>(initialCapacity   
         , loadFactor);   
 }   

// 调用 map 的 keySet 来返回所有的 key   

 public Iterator<E> iterator()   
 {   
     return map.keySet().iterator();   
 }   

 // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数  
 public int size()   
 {   
     return map.size();   
 }   

 // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,  
 // 当 HashMap 为空时,对应的 HashSet 也为空  
 public boolean isEmpty()   

 {   
     return map.isEmpty();   
 }   

 // 调用 HashMap 的 containsKey 判断是否包含指定 key   
 //HashSet 的所有元素就是通过 HashMap 的 key 来保存的  
 public boolean contains(Object o)   

 {   
     return map.containsKey(o);   
 }   

 // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap   
 public boolean add(E e)   
 {   
     return map.put(e, PRESENT) == null;   
 }   

 // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素  
 public boolean remove(Object o)   
 {   
     return map.remove(o)==PRESENT;   
 }   

 // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素  
 public void clear()   
 {   
     map.clear();   
 }   

 ...   

}   


✏️ 有任何想法?欢迎发邮件告诉老夫:daozhihun@outlook.com