【J2SE】hotspot中如何实现Object.hashCode

本文详细探讨了Java hotspot虚拟机中Object.hashCode的实现,包括基本概念、hotspot的实现以及测试验证。文章指出,hashCode的计算并不总是依赖于对象的内存地址,而是通过特定算法如Marsaglia's xor-shift scheme。对象的哈希值被存储在对象头的mark word中,即使经过GC,哈希值依然保持不变。同时,System.identityHashCode和Object.hashCode在hotspot中的实现相同。测试验证部分展示了哈希值在内存地址改变后的稳定性以及存储位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文目录

  • 一、基本概念
  • 二、hotspot的实现
    • Object.hashCode()
      • mark word
      • get_next_hash(thread, obj)
    • System.identityHashCode(obj)
  • 三、测试验证
    • 1.GC后对象内存地址改变但哈希值不变
    • 2.哈希值保存在对象头的mark word中
  • 附注:openjdk7未移除偏向锁源码

零、结论

  • hotspot中native hashCode方法计算对象的哈希值,JDK8以前是基于随机数实现,JDK8及以后是基于Marsaglia's xor-shift scheme(Marsaglia's移位异或方案)
  • 调用native hashCode方法计算出来的哈希值保存在对象的对象头中,但要注意该对象的锁状态:
    • 当对象已经调用过native hashCode方法,就无法再进入偏向锁状态;
    • 如果对象正处于偏向锁状态,此时调用其native hashCode方法,会撤销偏向并膨胀为重量级锁;
    • 如果对象正处于轻量级锁状态,此时对象头中保存的是指向栈中所锁记录的指针,对象的mark word则保存在锁记录中,此时调用其native hashCode方法:
      • 如果锁记录中保存的mark word已经有哈希值,则直接返回;
      • 否则轻量级锁也会膨胀为重量级锁,原因见源码注释:The displaced header is strictly immutable. It can NOT be changed in ANY cases. So we have to inflate the header into heavyweight monitor even the current thread owns the lock. 
    • 如果对象正处于重量级锁状态,那对象头中保存的指向堆中监视器monitor的指针,对象的mark word则保存在监视器monitor中;
      • 此时调用native hashCode,如果监视器monitor中保存的mark word已经哈希值则直接返回;
      • 如果监视器monitor中保存的mark word并没有包含哈希值,则要先计算出哈希值保存到监视器monitor中后再返回;
    • 偏向锁和轻量级锁通过monitor = inflate(current, obj, inflate_cause_hash_code)方法膨胀为重量级锁之后,走重量级锁的流程;

一、基本概念

API注释:Object (Java SE 15 & JDK 15) (oracle.com)

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

hashCode方法的返回值是一个int类型的哈希值,在J2SE集合框架那里一定会提到hashCode和equals方法的改写原则:

  • 运行时hashCode()方法计算的哈希值要保持不变:例如你重写了hashCode方法,其哈希值的计算依赖于对象的name+age+others,在运行时就算你修改了对象的name和age或其他相关属性,你也要保持hashCode方法的返回值不变,否则你将一个对象放入集合框架,随后修改了name或age,结果你发现你在集合框架中再也找不着这个对象了,因为hashCode返回的哈希值变了,定位到了table[]数组不同的slot,所以改写hashCode的第一个原则就是运行时hashCode()方法计算的哈希值要保持不变;
  • 如果两个对象通过equals方法相等,那么hashCode方法返回的哈希值也必须相等;
  • 反过来不作强制要求,即两个对象hashCode返回的哈希值相等,并不要求equals一定相等,但在集合框架中为了更好的性能,不同的对象尽量返回不同的哈希值;

Object.hashCode()方法是一个native方法,在不同版本的API documentation中,注释文档有些许差异:

 JDK8 API documentation:

This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.

JDK9~12 API documentation:

The hashCode may or may not be implemented as some function of an object's memory address at some point in time.

 JDK15 API documentation:

根据该方法不同版本的注释差异有:

  • JDK8以前,哈希方法的典型实现是将对象的内部地址转换为一个int值,但Java编程语言不必如此;
  • JDK9~12,Java哈希方法的实现可以依赖、也可以不依赖于对象的内存地址;
  • JDK13以后,直接去掉这段注释,估计是怕误导Java程序员。

这摸棱两可的说法,简直了。

对于Java程序员来说,虽然只需要知道这两个方法的改写原则就行,但是我们想了解的内容不止于此。

question

  1. 如果Java的哈希方法依赖于对象的内存地址,那GC时会移动对象,对象的内存地址会发生变化,Object.hashCode()方法是怎么保持哈希值不变的呢?
  2. 如果Java的哈希方法依赖于对象的内存地址,那GC时会移动对象,假设B对象移动到A对象的地址之后,B对象的hashCode()方法会返回相同的哈希值吗?
  3. 如若不依赖对象的内存地址,那Object.hashCode()方法又是如何实现的呢?
  4. 还有System.identityHashCode(object)又是怎么实现的呢?

接下来,我们去看看hotspot的源码。


二、hotspot的实现

1.Object.hashCode()

早期JVM版本(例如JDK8),Object.hashCode()方法在Object.c文件中声明,JDK15已经改到了jvm.h头文件中声明,本文也是基于JDK15的源码来作探究。

jdk/jvm.h at master · openjdk/jdk · GitHub

/*****************************************
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值