这是什么啊

19.hashcode内存溢出

我们在设计开发工程中,经常会用到HashMap,HashSet,HashTable等Java类,如果查看这些类的add,put等方法时,会发现这些方法都是基于参数对象的hashCode方法返回值经过一系列运算后,确定对象在哈希表中的位置。如果我们没有重写hashCode方法且所有父类也没有重写过Object的该方法,hashCode方法将使用Object类中native的hashCode方法,经验证,此方法产生的haseCode与子对象中的成员字段值无关系。

  下面通过一段代码,分析下如果重写hashCode方法,在使用不当时会怎样引起内存泄漏问题。

//代码1 public class HashSetOOM { public static void main(String[] args) { HashSet<MyClassA> set=new HashSet<MyClassA>(); for(int i=0;i<1000;i++){//如果你的虚拟机最大堆设置的比较大,可以调整这里的循环次数,多加几个对象进去 MyClassA a=new MyClassA(); a.data=new byte[2048000]; a.x=i; set.add(a); a.x=i+1; set.remove(a); a.hashCode(); } } } /**  * 我们定义一个类,该为了让该类的对象占用较大的空间,字段中使用一个byte[]数组。  * 此处重写了hasecode方法,方法使用eclipse自动生成代码。  * 同时重写equals方法,同样使用eclipse自动生成的代码 */ class MyClassA{  byte[] data;  int x;  @Override  public int hashCode() { final int prime = 31;   int result = 1;   result = prime * result + Arrays.hashCode(data);   result = prime * result + x;   return result;  }  @Override  public boolean equals(Object obj) {   if (this == obj)    return true;   if (obj == null)    return false;   if (getClass() != obj.getClass())    return false;   MyClassA other = (MyClassA) obj;   if (!Arrays.equals(data, other.data))    return false;   if (x != other.x)    return false;   return true;  } }

   通常,运行上边代码1,都会出现OOM情况。虽然,我们在循环的时候调用了set的remove方法。但是,事实情况下对象并没有从set中删除掉。我们查看add和remove方法时,会发现查找对象所在hash表中的位置时,是使用了这样一条逻辑:对象的“hashcode&tab.length-1”,操作是实际是表中该位置上的对象。如果,我们按上述代码修改后,hashcode将会发生改变,此时在进行remove的时候,可能将会出现两种情况:

  1、由于找不到此hashcode对应的index,remove不做任何操作。

  2、从hashset中删除了一个与之修改后生成对象的hashcode相等的对象。例如有这样一个类:

 

class Persion{  String name;//姓名  int age;//年龄  int weight;//体重  public Persion(String name,int age,int weight){   this.name=name;   this.age=age;   this.weight=weight;  }  @Override  public int hashCode() {   final int prime = 31;   int result = 1;   result = prime * result + age;   result = prime * result + ((name == null) ? 0 : name.hashCode());   result = prime * result + weight;   return result;  }  @Override  public boolean equals(Object obj) {   if (this == obj)    return true;   if (obj == null)    return false;   if (getClass() != obj.getClass())    return false;   Persion other = (Persion) obj;   if (age != other.age)    return false;   if (name == null) {    if (other.name != null)     return false;   } else if (!name.equals(other.name))    return false;   if (weight != other.weight)    return false;   return true;  } }
   我们定义了如下两个对象

 

Person person1=new Person("张三",60,70);//一个年龄为60岁,体重为70公斤的张三

Person person2=new Person("张三",71,60);//一个年龄为71岁,体重为60公斤的张三

将这两个对象都add到hashset中,后发现,person2的年龄错了,要修改成70岁,于是乎我们按上边的代码执行时,person1被从hashset中remove掉了,只是我们不想看到的。

 

  经过查阅资料及求助于各大搜索引擎发现,这种问题在设计层面竟然没有好的办法可以规避。出问题的点就在:重写hashcode方法后,修改成员字段数值会影响hashcode值,进而影响到对象在哈希表中的位置,致使remove方法无法找到原来的对象,但是,对象有在哈希表中有引用,hash对象又没有被释放。垃圾回收器发现其还有引用,又不会回收该对象。于是,内存泄漏了。

posted @ 2015-12-06 19:29  陈旭缘  阅读(351)  评论(0)    收藏  举报
这是什么