JDK核心JAVA源码解析(9) - hashcode 方法

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JDK核心JAVA源码解析(9) - hashcode 方法
本文基于 OpenJDK 11, HotSpot 虚拟机

在开发过程中我们可能会经常接触到hashcode这个方法来生成哈希码,那么底层是如何实现的?使用时有何注意点呢?


hashcode() 方法底层实现


hashcode()Object的方法:

@HotSpotIntrinsicCandidate
public native int hashCode();

它是一个native的方法,并且被@HotSpotIntrinsicCandidate注解修饰,证明它是一个在HotSpot中有一套高效的实现,该高效实现基于CPU指令。

具体的实现参考源码synchronizer.cpp

static inline intptr_t get_next_hash(Thread* self, oop obj) {
  intptr_t value = 0;
  if (hashCode == 0) {
    value = os::random();
  } else if (hashCode == 1) {
    intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
  } else if (hashCode == 2) {
    value = 1;           
  } else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
  } else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
  } else {
    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 &= markWord::hash_mask;
  if (value == 0) value = 0xBAD;
  assert(value != markWord::no_hash, "invariant");
  return value;
}


可以看出,根据hashcode这个全局变量的取值,决定用何种策略生成哈希值,查看globals.hpp来看是哪一种变量:

experimental(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")


发现是一个experimental的 JVM 变量,这样的话,想要修改,必须添加额外的参数,如下所示:

-XX:+UnlockExperimentalVMOptions -XX:hashCode=2

并且,这个hashCode默认为5。


哈希值是每次hashcode()方法调用重计算么?


对于没有覆盖hashcode()方法的类,实例每次调用hashcode()方法,只有第一次计算哈希值,之后哈希值会存储在对象头的 标记字(MarkWord) 中。


微信图片_20220624191838.jpg


(上图来自于:https://www.cnblogs.com/helloworldcode/p/11914053.html)

如果进入各种锁状态,那么会缓存在其他地方,一般是获取锁的线程里面存储,恢复无锁(即释放锁)会改回原有的哈希值

关于对象头结构,以及对象存储结构,感兴趣的话,可以参考:

张哈希:Java GC详解 - 1. 最全面的理解Java对象结构 - 对象指针 OOPs13 赞同 · 1 评论文章


-XX:hashCode=0 利用 Park-Miller 伪随机数生成器生成哈希值


if (hashCode == 0) {
    value = os::random();
}

调用 os 的 random 方法生成随机数。这个方法的实现方式是: os.cpp

//初始seed,默认是1
volatile unsigned int os::_rand_seed = 1;
static int random_helper(unsigned int rand_seed) {
  /* standard, well-known linear congruential random generator with
   * next_rand = (16807*seed) mod (2**31-1)
   * see
   * (1) "Random Number Generators: Good Ones Are Hard to Find",
   *      S.K. Park and K.W. Miller, Communications of the ACM 31:10 (Oct 1988),
   * (2) "Two Fast Implementations of the 'Minimal Standard' Random
   *     Number Generator", David G. Carta, Comm. ACM 33, 1 (Jan 1990), pp. 87-88.
  */
  const unsigned int a = 16807;
  const unsigned int m = 2147483647;
  const int q = m / a;        assert(q == 127773, "weird math");
  const int r = m % a;        assert(r == 2836, "weird math");
  // compute az=2^31p+q
  unsigned int lo = a * (rand_seed & 0xFFFF);
  unsigned int hi = a * (rand_seed >> 16);
  lo += (hi & 0x7FFF) << 16;
  // if q overflowed, ignore the overflow and increment q
  if (lo > m) {
    lo &= m;
    ++lo;
  }
  lo += hi >> 15;
  // if (p+q) overflowed, ignore the overflow and increment (p+q)
  if (lo > m) {
    lo &= m;
    ++lo;
  }
  return lo;
}
int os::random() {
  // Make updating the random seed thread safe.
  while (true) {
    unsigned int seed = _rand_seed;
    unsigned int rand = random_helper(seed);
    //CAS更新
    if (Atomic::cmpxchg(&_rand_seed, seed, rand) == seed) {
      return static_cast<int>(rand);
    }
  }
}

其中,random_helper 就是随机数的生成公式的实现,公式是:


image.png


这里,a=16807, c=0, m=2^31-1

由于这些随机数都是采用的同一个生成器,会 CAS 更新同一个 seed,如果有大量的生成的新对象并且都调用hashcode()方法的话,可能会有性能问题。重复调用同一个对象的hashcode()方法不会有问题,因为之前提到了是有缓存的。


-XX:hashCode=1或者4 基于对象指针 OOPs


OOPs(Ordinary Object Pointers)对象指针是对象头的一部分。关于对象头结构,以及对象存储结构,感兴趣的话,可以参考:Java GC详解 - 1. 理解Java对象结构。可以简单理解为对象在内存中的地址的描述。

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 addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
}
else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
}

cast_from_oop很简单,就是获取oop的实现基类oopDesc的指向地址(oopDesc描述了OOP的基本组成,感兴趣可以参考:Java GC详解 - 1. 理解Java对象结构):

template <class T> inline T cast_from_oop(oop o) {
  return (T)(CHECK_UNHANDLED_OOPS_ONLY((oopDesc*))o);
}

-XX:hashCode=4,直接用oop的地址作为哈希值。-XX:hashCode=1则是经过变换的,每次发生 Stop The World (STW)stw_random会发生改变,通过这个addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random变换减少哈希碰撞,让哈希值更散列化。

想更深入了解 Stop the world,可以参考:

张哈希:JVM相关 - SafePoint 与 Stop The World 全解30 赞同 · 15 评论文章


-XX:hashCode=2 敏感测试,恒定为1


else if (hashCode == 2) {
    value = 1;            // for sensitivity testing
}

主要用于测试某些集合是否对于哈希值敏感。


-XX:hashCode=3 自增序列


else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
}
struct SharedGlobals {
  // omitted
  DEFINE_PAD_MINUS_SIZE(1, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int) * 2);
  // Hot RW variable -- Sequester to avoid false-sharing
  volatile int hc_sequence;
  DEFINE_PAD_MINUS_SIZE(2, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int));
};
static SharedGlobals GVars;

每创建一个新对象,调用哈希值,这个自增数+1,可以看出,散列性极差,很容易哈希碰撞。


-XX:hashCode=5 默认实现


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;
}

采用的算法是 Marsaglia's xor-shift 随机数生成法。主要是这篇论文提出的一种快速并且散列性好的哈希算法。


特殊的哈希值导致某些场景的问题


我们经常使用某个对象或者某个字段的哈希值,通过对于某个数组长度取模,获取到下标,取出数组对应下标的对象,进行进一步处理。这在负载均衡,任务调度,线程分配很常见。那下面这段代码是否有问题呢?

//获取userId这个字符串的哈希值的绝对值
int index = Math.abs(userId.hashCode());
//返回哈希值取模之后的下标的对象
return userAvatarList.get(index % userAvatarList.size()).getUrl();


通常大多数情况下,是没有问题的,但是假设userId是这几个哈希值为Integer.MIN_VALUE的字符串:

System.out.println("polygenelubricants".hashCode());
System.out.println("GydZG_".hashCode());
System.out.println("DESIGNING WORKHOUSES".hashCode());

输出:

-2147483648
-2147483648
-2147483648


对于这些值,如果你用Math.abs()取绝对值的话,我们知道Math.abs(Integer.MIN_VALUE)还是等于Integer.MIN_VALUE,这是因为底层实现:

public static int abs(int a) {
    return (a < 0) ? -a : a;
}




-Integer.MIN_VALUEInteger.MIN_VALUE是相等的。Integer.MIN_VALUE取模还是负数,这样取下标对应的对象的时候,就会报异常

所以,需要修改为:

int index = Math.abs(userId.hashCode() % userAvatarList.size());
return userAvatarList.get(index).getUrl();
相关文章
|
17天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
17天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
17天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
60 12
|
1月前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
18天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
2月前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
29 1
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
96 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
89 0
|
3月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
69 0

推荐镜像

更多