?
我们在设计开发工程中,经常会用到HashMap,HashSet,HashTable等Java类,如果查看这些类的add,put等方法时,会发现这些方法都是基于参数对象的hashCode方法返回值经过一系列运算后,确定对象在哈希表中的位置。如果我们没有重写hashCode方法且所有父类也没有重写过Object的该方法,hashCode方法将使用Object类中native的hashCode方法,经验证,此方法产生的haseCode与子对象中的成员字段值无关系。
下面通过一段代码,分析下如果重写hashCode方法,在使用不当时会怎样引起内存泄漏问题。
class="java" name="code">//代码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对象又没有被释放。垃圾回收器发现其还有引用,又不会回收该对象。于是,内存泄漏了。
总结:重写hashcode方法的类对象,在放入hash表后,一定要谨慎操作对象字段属性值。如果要修改,先从哈希表中删除该对象,修改后再放入哈希表。
?
?