hashCode 是 java.lang.Object.hashCode() 或者 java.lang.System.identityHashCode(obj) 会返回的值。他是一个对象的身份标识。官方称呼为:标识哈希码( identity hash code)

hashcode 的特点

  • 一个对象在其生命期中 identity hash code 必定保持不变
  • 如果 a == b,那么他们的 System.identityHashCode() 必须相等
  • 如果 System.identityHashCode() 相等的话,并不能保证 a == b(毕竟这只是一个散列值,是允许冲突的)

为什么hashCode()性能高

因为 hashCode()的结果算出来后缓存起来,下次调用直接用不需要重新计算,提高性能

identityHashCode

看官方提供的 API , 对 System.identityHashCode()的解释为 :
public static int identityHashCode ([Object] x)

返回给定对象的哈希码,该代码与默认的方法 hashCode() 返回的代码一样,无论给定对象的类是否重写 hashCode()。null 引用的哈希码为 0。

 @Test
    public void testHashCode() {
        TestExample example = new TestExample();
        int exampleCode = System.identityHashCode(example);
        int exampleCode2 = System.identityHashCode(example);
        int exampleHashcode = example.hashCode();
        System.out.println("example identityHashCode:" + exampleCode);
        System.out.println("example2 identityHashCode:" + exampleCode2);
        System.out.println("example Hashcode:" + exampleHashcode);
        String str = "dd";
        String str2 = "dd";
        int strCode = System.identityHashCode(str);
        int strHashCode = str.hashCode();
        int str2HashCode = str.hashCode();
        System.out.println("str identityHashCode:" + strCode);
        System.out.println("str hashcode:" + strHashCode);
        System.out.println("str2 hashcode:" + str2HashCode);
    }
输出:
example identityHashCode:1144748369
example2 identityHashCode:1144748369
example Hashcode:1144748369
str identityHashCode:340870931
str hashcode:3200
str2 hashcode:3200 
 

hashCode 如何计算的

public native int hashCode(); 

即该方法是一个本地方法,Java 将调用本地方法库对此方法的实现。由于 Object 类中有 JNI 方法调用,按照 JNI 的规则,应当生成 JNI 的头文件,在此目录下执行 javah -jni java.lang.Object 指令,将生成一个 java_lang_Object.h 头文件

/*
 * Class:     java_lang_Object
 * Method:    hashCode
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_java_lang_Object_hashCode
  (JNIEnv *, jobject); 
 
/*-
 *      Implementation of class Object
 *
 *      former threadruntime.c, Sun Sep 22 12:09:39 1991
 */
#include <stdio.h>
#include <signal.h>
#include <limits.h>
#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
//使用了上面生成的java_lang_Object.h文件
#include "java_lang_Object.h"
static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},//hashcode的方法指针JVM_IHashCode
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
}; 
 

生成 hash 最终由 get_next_hash 函数生成,该函数提供了基于某个 hashCode 变量值的六种方法。怎么生成最终值取决于 hashCode 这个变量值

6 种 hashCode 算法

0 - 使用 Park-Miller 伪随机数生成器(跟地址无关)
1 - 使用地址与一个随机数做异或(地址是输入因素的一部分)
2 - 总是返回常量 1 作为所有对象的 identity hash code(跟地址无关)
3 - 使用全局的递增数列(跟地址无关)
4 - 使用对象地址的“当前”地址来作为它的 identity hash code(就是当前地址)
5 - 使用线程局部状态来实现 Marsaglia’s xor-shift 随机数生成(跟地址无关)

VM 到底用的是哪种方法?

在 openjdk\hotspot\src\share\vm\runtime\globals.hpp 定义
JDK 8 和 JDK 9 默认值:

product(intx, hashCode, 5,“(Unstable) select hashCode generation algorithm”) ;

JDK 8 以前默认值:

product(intx, hashCode, 0,“(Unstable) select hashCode generation algorithm”) ;
不同的 JDK,生成方式不一样。

注意:
虽然方式不一样,但有个共同点:java 生成的 hashCode 和对象内存地址没什么关系

HotSpot 提供了一个 VM 参数来让用户选择 identity hash code 的生成方式:
-XX:hashCode

什么时候计算出来的

在 VM 里,Java 对象会在首次真正使用到它的 identity hash code(例如通过 Object.hashCode() / System.identityHashCode())时调用 VM 里的函数来计算出值,然后会保存在对象里,后面对同一对象查询其 identity hash code 时总是会返回最初记录的值。

因此,不是对象创建时。

对此以 Hotspot 为例,最直接的实现方式就是在对象的 header 区域中划分出来一部分(32位机器上是占用25位,64位机器上占用31)用来存储 hashcode 值。但这种方式会添加额外信息到对象中,而在大多数情况下 hashCode 方法并不会被调用,这就造成空间浪费。

那么JVM是如何进行优化的呢?当hashCode方法未被调用时,object header中用来存储hashcode的位置为0,只有当hashCode方法(本质上是System#identityHashCode)首次被调用时,才会计算对应的hashcode值,并存储到object header中。当再次被调用时,则直接获取计算好hashcode即可。

上述实现方式就保证了即使 GC 发生,对象地址发生了变化,也不影响 hashcode 的值。比如在 GC 发生前调用了 hashCode 方法,hashcode 值已经被存储,即使地址变了也没关系;在 GC 发生后调用 hashCode 方法更是如此

// 创建对象并打印JVM中对象的信息
Object person = new Object();
System.out.println(ClassLayout.parseInstance(person).toPrintable());
// 调用hashCode方法,如果重写了hashCode方法则调用System#identityHashCode方法
System.out.println(person.hashCode());
// System.out.println(System.identityHashCode(person));
// 再次打印对象JVM中的信息
System.out.println(ClassLayout.parseInstance(person).toPrintable());
 

执行上述程序,控制台打印如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1898220577
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 21 8c 24 (00000001 00100001 10001100 00100100) (613163265)
      4     4        (object header)                           71 00 00 00 (01110001 00000000 00000000 00000000) (113)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

在调用hashCode方法前后,我们可以看到OFFSET为0的一行存储的值(Value),从原来的1变为613163265,也就是说将hashcode的值进行了存储。如果未调用对应方法,则不会进行存储 。