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实现代码(部分)

  1. public V put(K key, V value) {   

  2.         if (key == null)   

  3.             return putForNullKey(value);   

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

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

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

  7.             Object k;   

  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   

  9.                 V oldValue = e.value;   

  10.                 e.value = value;   

  11.                 e.recordAccess(this);   

  12.                 return oldValue;   

  13.             }   

  14.         }   

  15.   

  16.         modCount++;   

  17.         addEntry(hash, key, value, i);   

  18.         return null;   

  19.     }  


  附录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();   

 }   

 …   

}   


Java的内存泄漏——HashSet和hashCode”的一个响应

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s