为什么想到要写这个话题呢?源自于笔者遇到这样一个场景:对象A中包括一个私有属性对象B,对象B很大并且常驻内存,影响了服务的gc性能,所以打算使用OHCache将对象转移到堆外,每次要用到B时从堆外内存取出A所对应B,如果对堆外内存如何使用不清楚的朋友可以参考这篇文章:使用堆外内存优化服务 。大概代码思路如下所示:
class A {
private B b;
private class B {
//大对象,常驻内存
}
private static OHCache<Integer, B> ohCache = OHCacheBuilder.<String, Big>newBuilder().build();
}
这样就需要为每个A生成一个独一无二的int值,便于从OHCache中取出属于它的B。这里方法很多,比如UUID、雪花算法什么的,但是笔者首先想到的是,能否使用hashCode作为key呢?但是紧接着一连串的问题就过来了:
到这儿才觉得自己对hashCode的理解不够,准备好好了解下。首先说结论:
关于第二点可以从hashCode的官方注释上得到证实:如下图所示:

关于第一点,搜索了下相关资料有很多,笔者就没有深入源码了解,况且笔者也不懂C。在此只是转述其他人的分析,Object#hashCode方法的生成规则一共有六种:
默认Java程序为我们选择最后一种,我们也可以通过运行是参数-XX:hashCode=N来配置,从0开始,默认值为5。
接下来我们继续讨论第二点。这个比较好理解,如果对象不同,object#hashCode可以一样。因为对象的个数是无限的,但是hashCode作为一个Int的返回结果,是有上限的。这里顺便提一下,即使两个对象的hashCode一样,是不能保证两个对象相同的,还需要equals方法的返回结果为true。那么Java是如何保证同一个对象的hashCode始终不变的呢,毕竟如果hashCode是使用对象的地址作为结果的话,对象的地址是会变化的(gc来回复制)。这个答案是:hashCode的值只会计算一次,然后存储在对象头的MarkWord中,后续不会再重新计算,直接从对象头中去获取。对象头的结构如下所示:

可见在对象处于无锁状态下,对象头中存储的信息及时hashCode。那么不禁要问,如果对象被当成锁对象被持有了呢,那对象头中就没有hashCode信息。的确是在,从JDK1.6开始,锁有四种状态:无锁->偏向锁->轻量级锁->重量级锁。对synchronize底层熟悉的同学应该清楚(如果不清楚的同学,可以参考这篇:synchronize底层原理解析,除了重量级锁,其他情况的锁都会在持有锁的线程栈帧中保存对象头信息。而当对象处于重量级锁的时候,重量级锁指向了一个ObjectMonitor对象,在其中也应该了记录hashCode信息,猜测应该在Header或者object属性中。
参考资料:
https://www.javazhiyin.com/88059.html
https://juejin.cn/post/6971946031764209678
https://www.codenong.com/cs106603249