java Object的hashCode方法的计算逻辑分析

目录
  • 1. 背景介绍
  • 2. 调用过程梳理
    • java的Object代码
    • jvm的hashCode相关代码
  • 3. 关于hashCode值的大小
  • 4. 验证
  • 5. 总结

1. 背景介绍

在为重写hashCode方法的时候,看到hashCode打印出的数据像是一个地址值,很是好奇。

加之最近在研读jvm源码,特此一探究竟,看看在hotspot中hashCode究竟是如何实现的。

2. 调用过程梳理

java的Object代码

public native int hashCode();

通过官产jdk的Object.class的源码, 发现hashCode被native修饰. 因此这个方法应该是在jvm中通过c/c++实现

jvm的hashCode相关代码

首先观察Object.java对应的Object.c代码

// 文件路径: jdk\src\share\native\java\lang\Object.c
static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode}, // 这个方法就是我们想看的hashCode方法
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

进一步进入到jvm.h文件中, 这个文件中包含了很多java调用native方法的接口

// hotspot\src\share\vm\prims\jvm.h
/*
 * java.lang.Object
 */
JNIEXPORT jint JNICALL
JVM_IHashCode(JNIEnv *env, jobject obj); // 此时定了已hashCode方法的接口, 具体实现在jvm.cpp中
// hotspot\src\share\vm\prims\jvm.cpp
// 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)) ; // 如果object为null, 就返回0; 否则就调用ObjectSynchronizer::FastHashCode
JVM_END

进入到ObjectSynchronizer::FastHashCode

// hotspot\src\share\vm\runtime\synchronizer.cpp
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
// ....
 // 在FastHashCode方法中有一段关键代码:
 if (mark->is_neutral()) {
     hash = mark->hash();              // 首先通过对象的markword中取出hashCode
     if (hash) {                       // 如果取调到了, 就直接返回
       return hash;
     }
     hash = get_next_hash(Self, obj);  // 如果markword中没有设置hashCode, 则调用get_next_hash生成hashCode
     temp = mark->copy_set_hash(hash); // 生成的hashCode设置到markword中
     // use (machine word version) atomic operation to install the hash
     test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
     if (test == mark) {
       return hash;
     }
 }
// ....
}

生成hashCode的方法get_next_hash, 可以支持通过参数配置不同的生成hashCode策略

// hotspot\src\share\vm\runtime\synchronizer.cpp
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  // 一共支持6中生成hashCode策略, 默认策略值是5
  if (hashCode == 0) {
  // 策略1: 直接通过随机数生成
     value = os::random() ;
  } else if (hashCode == 1) {
     // 策略2: 通过object地址和随机数运算生成
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else if (hashCode == 2) {
  // 策略3: 永远返回1, 用于测试
     value = 1 ;            // for sensitivity testing
  } else if (hashCode == 3) {
  // 策略4: 返回一个全局递增的序列数
     value = ++GVars.hcSequence ;
  } else if (hashCode == 4) {
  // 策略5: 直接采用object的地址值
     value = cast_from_oop<intptr_t>(obj) ;
  } else {
     // 策略6: 通过在每个线程中的四个变量: _hashStateX, _hashStateY, _hashStateZ, _hashStateW
     // 组合运算出hashCode值, 根据计算结果同步修改这个四个值
     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; // 通过hashCode的mask获得最终的hashCode值
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

3. 关于hashCode值的大小

前面以及提交到hashCode生成后, 是存储在markword中, 我们在深入看一下这个markword

// hotspot\src\share\vm\oops\markOop.hpp
class markOopDesc: public oopDesc {
 private:
  // Conversion
  uintptr_t value() const { return (uintptr_t) this; }
 public:
  // Constants
  enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits, // 通过这个定义可知, hashcode可占用31位bit. 在32位jvm中,  hashCode占用25位
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };

}

4. 验证

package test;
/***
 * 可以通过系列参数指定hashCode生成策略
 * -XX:hashCode=2
 */
public class TestHashCode {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();
        System.out.println(obj1.hashCode());
        System.out.println(obj2.hashCode());
    }
}

通过-XX:hashCode=2这种形式, 可以验证上述的5中hashCode生成策略

5. 总结

在64位jvm中, hashCode最大占用31个bit; 32位jvm中, hashCode最大占用25个bit

hashCode一共有六种生成策略

序号 hashCode策略值 描述
1 0 直接通过随机数生成
2 1 通过object地址和随机数运算生成
3 2 永远返回1, 用于测试
4 3 返回一个全局递增的序列数
5 4 直接采用object的地址值
6 其他 通过在每个线程中的四个变量: _hashStateX, _hashStateY, _hashStateZ, _hashStateW 组合运算出hashCode值, 根据计算结果后修改这个四个值

默认策略采用策略6, 在globals.hpp文件中定义

  product(intx, hashCode, 5,                                                \
          "(Unstable) select hashCode generation algorithm")  

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 搞懂JAVAObject中的hashCode()

    目录 Object中的hashCode() hashCode()和equals() 重写的hashCode()方法 总结 Object中的hashCode() hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下: public native int hashCode(); 这是一个native声明的本地方法,返回一个int型的整数.由于在Object中,因此每个对象都有一个默认的哈希值. 在openjdk8

  • 关于Java中HashCode方法的深入理解

    1.0前言 最近在学习 Go 语言,Go 语言中有指针对象,一个指针变量指向了一个值的内存地址.学习过 C 语言的猿友应该都知道指针的概念.Go 语言语法与 C 相近,可以说是类 C 的编程语言,所以 Go 语言中有指针也是很正常的.我们可以通过将取地址符&放在一个变量前使用就会得到相应变量的内存地址. package main import "fmt" func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量

  • 浅谈Java中hashCode的正确求值方法

    本文研究的主要是Java中hashCode的正确求值方法的相关内容,具体如下. 散列表有一项优化,可以将对象的散列码(hashCode)缓存起来,如果散列码不匹配,就不会检查对象的等同性而直接认为成不同的对象.如果散列码(hashCode)相等,才会检测对象是否相等(equals). 如果对象具有相同的散列码(hashCode),他们会被映射到同一个散列桶中.如果散列表中所有对象的散列码(hashCode)都一样,那么该散列表就会退化为链表(linked list),从而大大降低其查询效率. 一

  • 深入理解Java中HashCode方法

    关于hashCode,维基百科中: In the Java programming language, every class implicitly or explicitly provides a hashCode() method, which digests the data stored in an instance of the class into a single hash value (a 32-bit signed integer). hashCode就是根据存储在一个对象实例

  • java Object的hashCode方法的计算逻辑分析

    目录 1. 背景介绍 2. 调用过程梳理 java的Object代码 jvm的hashCode相关代码 3. 关于hashCode值的大小 4. 验证 5. 总结 1. 背景介绍 在为重写hashCode方法的时候,看到hashCode打印出的数据像是一个地址值,很是好奇. 加之最近在研读jvm源码,特此一探究竟,看看在hotspot中hashCode究竟是如何实现的. 2. 调用过程梳理 java的Object代码 public native int hashCode(); 通过官产jdk的O

  • 浅谈Java中的hashcode方法(推荐)

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

  • Java中的hashcode方法介绍

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

  • java object 之clone方法全面解析

    1 protected native Object clone() throws CloneNotSupportedException; 1.方法由native关键字修饰 java中的native关键字表示这个方法是个本地方法,[java native说明].而且native修饰的方法执行效率比非native修饰的高. 2.方法由protected修饰 一个类在覆盖clone()方法时候,需要修改成public访问修饰符,这样才能保证其他所有的类都能够访问这个类的这个方法. 3.方法抛出Clon

  • Java Object类equals方法

    基本概念: Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入: Object类是所有Java类的祖先.每个类都使用 Object 作为超类.所有对象(包括数组)都实现这个类的方法.可以使用类型为Object的变量指向任意类型的对象 equals()方法:比较两个对象是否同一       如果两个对象具有相同的类型以及相同的属性值,则称这两个对象相等.如果两个引用对象指的是同一个对像,则称这两个变量同一.Object类中定义的equa

  • java中的hashCode方法小例子

    在java中,有一个这样的规定,就是两个相同的对象(即equals运算为true),它们的hash code也必须相同.在Object类中有一个hashCode方法,可以调用它来查看对象的hash code.下面举例说明. 复制代码 代码如下: package test; public class Test { public static void main(String args[]){  String str1 = "aaa";  String str2 = str1;  Stri

  • 探索Java中的equals()和hashCode()方法_动力节点Java学院整理

    equals()和hashCode()区别?  equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值.  hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数.根类Object的hashCode()方法的计算依赖于对象实例的D(内存地址),故每个Object对象的hashCode都是唯一的:当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了. 之所以有hashCode方法,是因为在批量的对象比

  • java集合——Java中的equals和hashCode方法详解

    Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用. equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复. 这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等.换

  • Java hashCode() 方法详细解读

    1.WHY hashCode()? 集合Set中的元素是无序不可重复的,那判断两个元素是否重复的依据是什么呢? "比较对象是否相等当然用Object.equal()了",某猿如是说.但是,Set中存在大量对象,后添加到集合Set中的对象元素比较次数会逐渐增多,大大降低了程序运行效率. Java中采用哈希算法(也叫散列算法)来解决这个问题,将对象(或数据)依特定算法直接映射到一个地址上,对象的存取效率大大提高.这样一来,当含有海量元素的集合Set需要添加某元素(对象)时,先调用这个元素的

随机推荐