hashCode
概述
Java中hashCode也称哈希码,能将对象根据一定算法转换为32位int值。
和equals配套使用,重写的话需要保持原子性,要么都重写,要么都不重写。equals相同的话哈希值一定相同,哈希值不同的话equals不一定相同,存在hash冲突。
hash冲突
-
开放定址法:将对象值hash出现冲突后,根据结果值再次hash,直到结果不冲突
-
链地址法:建立链表,冲突的hash值用链表连接
-
建立公共溢出区:分基本表和溢出区,将冲突的hash值放进溢出区
-
再哈希法:多个hash函数,一个重复换另一个
生成策略
JVM启动参数中,添加-XX:hashCode=值,可以控制hashCode的计算方式。共6中生成策略,值为0,1,2,3,4,其他
0 - 用Park-Miller伪随机数生成器
1 - 地址和随机数异或运算
2 - 总返回常量1
3 - 使用全局递增序列
4 - 对象地址的当前地址
5 - 线程局部状态实现Marsaglia‘s异或-位移随机数生成(有兴趣了解 ==> Marsaglia`s Xorshift Random Number Generators)
其中仅有1,4生成结果和地址有关,JDK8以前默认是1,JDK8之后默认是5
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) {//这与第5个方式类似,都调用了cast_from_oop函数,只不过此处又增加了位偏移和addrBits ^ (addrBits >> 5) ^ GVars.stwRandom计算
// 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 异或-位移方案
// 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;
}
存储位置
对象,保存在JVM堆区域中,此区域线程共享。在HotSpot虚拟机内,对象在堆内存中存储布局分三部分:对象头,实例数据、对齐填充。
其中对象头存放两类数据:运行时数据、类型指针
运行时数据有哈希码、GC分代年龄、锁状态标识、线程持有锁、偏向线程ID、偏向时间戳等。该部分数据在32位和64位JVM上分别为32bit和64bit,官方称为’Mark Word‘。例如32位HotSpot虚拟机中,在对象未被同步锁锁定的情况下,Mark Word 32个比特空间中25个用来存哈希码、4个存分代年龄、2个存锁标识位、1个为0。其他情况如下表:
类型指针即对象指向它类型元数据的指针,JVM通过该指针来判断此对象是哪个类型的实例。但并非所有JVM都要在对象头保留类型指针,查找对象类型不一定非要经过对象本身。此外若对象是个数组,则头中还要有个数据记录数组长度。