我们在设计开发工程中,经常会用到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对象又没有被释放。垃圾回收器发现其还有引用,又不会回收该对象。于是,内存泄漏了。
总结:重写hashcode方法的类对象,在放入hash表后,一定要谨慎操作对象字段属性值。如果要修改,先从哈希表中删除该对象,修改后再放入哈希表。
相关推荐
内存管理和优化是编程中至关重要的环节,尤其是在Java这样的高级语言中,由于自动内存管理机制的存在,内存泄漏和溢出问题可能会变得难以察觉但后果严重。本文将深入探讨内存泄漏及其可能导致的内存溢出问题,以及...
### C# Equals 和 GetHashCode 方法重写 在C#编程中,`Equals` 和 `GetHashCode` 方法是非常重要的成员方法,它们对于确保对象的正确比较以及高效地存储和检索对象至关重要。这两个方法通常需要在自定义类中进行...
如果对象需要放入散列容器(如HashSet或HashMap),则还需重写hashcode()方法,以确保根据内容相等的对象具有相同的哈希码。equals()相等的对象,其hashcode()必须相等,但反之不成立,因为哈希冲突可能导致不同的...
Java中的`hashCode()`方法是基于对象的内部状态计算出的一个整数值,它在处理...在实现自定义类时,正确地重写`hashCode()`和`equals()`方法可以确保对象在哈希表中的行为符合预期,并且能有效地利用哈希表的性能优势。
当重写equals()方法时,务必同时重写hashCode()方法,以保持对象比较的一致性。否则,这可能导致HashSet或HashMap的行为异常。 九、资源未释放 数据库连接、网络连接等资源使用后应及时关闭。使用finally块或try-...
如果没有重写`hashCode()`,当一个`PhoneNumber`对象作为键放入`HashMap`时,即使`equals()`方法返回`true`表示两个电话号码相等,由于它们的`hashCode()`可能不同,`HashMap`可能会将它们视为不同的键,导致预期的...
4. **内存泄漏**:未正确释放对象引用,导致内存无法回收,长期积累可能导致内存溢出。合理使用` WeakReference`和`SoftReference`,及时清理不再使用的对象,可以帮助防止内存泄漏。 5. **异常处理不当**:忽略...
内存溢出通常由于内存分配过多或无法回收导致,常见类型有堆溢出、栈溢出和 PermGen(方法区)溢出。 20. **JVM类加载机制**: 类加载分为加载、验证、准备、解析和初始化五个阶段,确保类正确加载并准备执行。 ...
这只是Java面试中一小部分可能遇到的问题,面试中还可能涉及多线程、集合框架、IO流、异常处理、设计模式、反射、JVM内存模型、垃圾回收机制、数据库连接、网络编程、Spring框架等相关知识。全面掌握这些概念和实践...
本文将详细解答在Java面试中可能会遇到的一些核心问题,这些问题涵盖了Java语言的基础概念、类库使用、异常处理、内存管理、多线程、设计模式等多个方面。以下是针对这些面试问题的详细解释: 1. **面向对象的特征*...
然而,这里出现了一个问题:类A和类B之间存在循环依赖,并且两者都重写了`toString()`方法。 类A中包含一个B类型的属性,并在`toString()`方法中调用了`b.toString()`。同样,类B也有一个A类型的属性,并在其`...
6. **使用equals()与hashCode()的约定**:当重写equals()时,确保同时重写hashCode(),以保持散列码的一致性。 7. **避免在循环中使用同步方法或锁**:这会导致线程阻塞,影响性能。可以考虑使用并发容器,如...
- 自定义对象作为key时,必须重写equals()和hashCode()方法,以确保对象比较逻辑的一致性,并且key对象应是不可变的,以防止后续修改导致的查找问题。 9. **线程启动与控制** - **启动线程**:使用`start()`,`...
- 使用`sun.misc.Unsafe`或`ByteBuffer`可以分配大内存,但需注意释放,否则可能导致内存泄漏。 20. **JVM配置和调优**: - 常见参数如-Xms、Xmx设置堆大小,-XX:NewRatio调整新生代与老年代比例,-XX:+UseG1GC...
- 理解内存泄漏和内存溢出的问题,以及如何通过JVM参数进行调优。 以上只是Java面试中可能会遇到的部分主题,实际面试中还会涉及其他高级话题,如JVM原理、Spring框架、数据库操作、分布式系统等。为了在面试中...
17. Error 通常表示系统级问题,如内存溢出,无法恢复;Exception 表示程序中的可预期错误,可以捕获处理。 18. List, Set, Map 分别继承自 Collection 接口的子接口:List 继承自 List 接口,Set 继承自 Set 接口...
Java中的内存溢出通常由于以下几个原因造成: PermGen Space不足(存放类信息的空间),可以通过调整JVM参数解决;Java堆内存过载,导致过多对象创建,可以通过调整堆大小限制来防止;线程创建过多,超出系统资源...
- **JVM内存管理**:包括堆内存(Heap)、栈内存(Stack)、方法区(Method Area)等区域,理解它们的工作原理有助于解决内存溢出等问题。 - **GC回收机制**:Java的垃圾收集器负责回收不再使用的对象,有多种算法...
重写equals通常需要同时重写hashCode,以保证哈希表的正确性。 17. **Java序列化**:序列化将对象转换为字节流,便于存储或网络传输;反序列化反之。Serializable接口标记可序列化的类。 18. **Object类方法**:...