关于Java中HashCode的一些思考
最近在重写hashCode和equals方法时,突然很想知道hashCode是如何计算而来的。于是便下载了openJdk1.8的源码,结合网上一些博客学习了一下,并编写代码进行了测试和总结。
1. Object类中的hashCode()方法
public native int hashCode();
这是一个natvie方法,当一个类没有重写hashCode方法时,默认调用的hashCode就是Object类中的hashCode。那么Object类中的hashCode是如何计算而来的呢?
2. 找到对应的native方法源码实现
源码环境:openjdk1.8
在源码目录:openjdk\jdk\src\share\native\java\lang\Object.c
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
hashCode由JVM_IHashCode指针来处理
在源码目录:openjdk\hotspot\src\share\vm\prims\jvm.cpp , 找到JVM_IHashCode指针
// java.lang.Object ///
JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
JVMWrapper("JVM_IHashCode");
// as implemented in the classic virtual machine; return 0 if object is NULL
return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END
计算hashCode的方法为ObjectSynchronizer::FastHashCode
在源码目录:openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp ,找到ObjectSynchronizer::FastHashCode方法
// Inflate the monitor to set hash code
monitor = ObjectSynchronizer::inflate(Self, obj);
// Load displaced header and check it has hash code
mark = monitor->header();
assert (mark->is_neutral(), "invariant") ;
hash = mark->hash();
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
assert (temp->is_neutral(), "invariant") ;
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
if (test != mark) {
// The only update to the header in the monitor (outside GC)
// is install the hash code. If someone add new usage of
// displaced header, please update this code
hash = test->hash();
assert (test->is_neutral(), "invariant") ;
assert (hash != 0, "Trivial unexpected object/monitor header usage.");
}
}
// We finally get the hash
return hash;
ObjectSynchronizer::FastHashCode方法的最后代码如上,从对象头中获取hash,如果没有就生成一个hash设置到对象头中,生成hash的方法为get_next_hash(Self, obj)
在源码目录:openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp ,找到get_next_hash方法
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = cast_from_oop<intptr_t>(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
这里生成hash的方法主要有如下几种:
-
hashCode == 0
返回的是由伪随机生成器生成的一个随机数
-
hashCode == 1
返回的是对象的内存地址做了移位和异或运算后的结果数
-
hashCode == 2
返回的是不变的1
-
hashCode == 3
返回的递增的序列
-
hashCode == 4
返回的是对象的内存地址
-
hashCode == 5
返回的是一个与当前线程有关的随机数与其他三个固定值进行xorshift运算后的结果数
在源码目录:openjdk\hotspot\src\share\vm\runtime\thread.cpp ,找到固定值
// thread-specific hashCode stream generator state - Marsaglia shift-xor form _hashStateX = os::random() ; _hashStateY = 842502087 ; _hashStateZ = 0x8767 ; // (int)(3579807591LL & 0xffff) ; _hashStateW = 273326509 ;
jdk1.8中默认使用的是hashCode == 5
在源码目录:openjdk\hotspot\src\share\vm\runtime\globals.hpp
product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")
3. 测试hashCode的生成
测试环境:jdk1.8,测试代码如下:
public class HashCodeTest
{
public static void main(String... args) {
User user1 = new User("小明1", 18);//User类只有name和age 两个属性
User user2 = new User("小明2", 18);
System.out.println("小明1: hashcode: "+ user1.hashCode());
System.out.println("小明2: hashcode: "+ user2.hashCode());
}
}
-
测试默认情况
两次结果分别是:
同一个对象的两次的hashCode值相同
-
测试hashCode==0的情况:
两次的结果分别是:
同一个对象两次的hashCode都不相同 -
测试hashCode==1的情况:
两次的结果分别是:
同一个对象两次的hashCode值相同,修改堆的初始值后:
hashCode发生了变化 -
测试hashCode==2的情况
两次的结果分别是:
hashCode值均为1 -
测试hashCode==3的情况:
两次的结果分别是:
hashCode为递增的关系 -
测试hashCode==4的情况:
两次的结果分别是:
两次的hashCode值相同,修改堆的初始值后:
hashCode发生了变化 -
测试hashCode==5的情况:
两次的结果分别是:
两次的hashCode值相同,修改堆的初始值后:
hashCode值仍先相同。并且测试7与测试1返回的结果中hashCode是相同的,印证了上述中jdk1.8中默认使用的hashCode==5这种方式。
4. 总结
jdk1.8中hashCode的生成是与当前线程有关的随机数和其他三个固定值进行xorshift运算后的结果数。在自定义的类中,如果只重写equals方法而不重写hashCode方法,会出现两个属性均相同的对象,通过equals方法比较是相等的,但两个对象的hashCode值却不相同(因为根据hashCode==5的生成方式,hashCode的生成与对象中的属性无关)。这种情况在操作实现方式为哈希表的一些数据结构时可能就会出现问题,比如equals方法判定为相同的对象却因hashCode不同而被认为是不同的对象。