hashCode

lingxing_beijing-005

概述

​ 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。其他情况如下表:

20220415165627

​ 类型指针即对象指向它类型元数据的指针,JVM通过该指针来判断此对象是哪个类型的实例。但并非所有JVM都要在对象头保留类型指针,查找对象类型不一定非要经过对象本身。此外若对象是个数组,则头中还要有个数据记录数组长度。

参考文章

hash冲突的4种解决方案

java本地方法 hashcode是怎样生成的?hashcode与地址有关系吗?

深入理解Java虚拟机 第三版

posted @ 2021-03-13 11:35  lifelikeplay  阅读(105)  评论(0编辑  收藏  举报