温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何实现译文jdk默认hashCode方法

发布时间:2021-10-09 15:57:38 来源:亿速云 阅读:144 作者:iii 栏目:编程语言

这篇文章主要介绍“如何实现译文jdk默认hashCode方法”,在日常操作中,相信很多人在如何实现译文jdk默认hashCode方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何实现译文jdk默认hashCode方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一个不起眼的小问题

上周的工作中我向一个类提交了一个微不足道的变化,实现toString()方法用来让日志更有用。令我惊讶的是,变化导致约5%的覆盖率下降。我知道所有新代码都被现有的单元测试覆盖,但是覆盖率下降了,所以哪里出了问题?
对比之前的覆盖范围报告,一个敏锐的同事发现,在代码之前单元测试覆盖了HashCode()的实现,但改动之后就没有覆盖。当然,这是对的:默认的ToString()调用hashcode(),修改后的没有。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

重写了toString之后,我们自定义的hashCode不再被调用,所以覆盖率下降了。所有人都知道默认的toString的实现原理,但是...

默认的hashCode方法怎么实现的?

默认的hashCode()返回的是唯一hash码(identity hash code),注意这个和重写hashCode返回的hash码不是一个东西,如果某个类我们重写了hashCode方法,我们还可以使用System.identityHashCode(o)来获取它的唯一hash码(感觉这个就是对象的身份证号)。
大家普遍认为唯一hash码使用的是对象内存地址的对应的整数(内存整理对象移动了咋办?),不过java api文档是这么说的:

... 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.
典型的实现方式是把对象的内存地址转为一个整数,但是这种实现技术并不是java平台必需的

鉴于JVM将重新定位对象(例如在垃圾收集期间由于晋升或压缩),在我们计算对象的身份哈希码之后,我们必须保留它。

默认的hashCode实现

对于默认的hashCode方法,不同的JVM可能实现的方式不一样,本文只看openJDK的源码,hashCode是native方法,入口如下:src/share/vm/prims/jvm.hsrc/share/vm/prims/jvm.cpp

508 JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
509   JVMWrapper("JVM_IHashCode");
510   // as implemented in the classic virtual machine; return 0 if object is NULL
511   return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
512 JVM_END

然后是ObjectSynchronizer::FastHashCode()文件是src/share/vm/runtime/synchronizer.cpp 人们可能天真的以为方法像下面这么简单:

if (obj.hash() == 0) {
    obj.set_hash(generate_new_hash());
}
return obj.hash();

但实际上有几百行...看文件名也大概知道此处涉及到同步,也就是synchronized的实现,是的,就是对象内置锁。这个随后再讨论,先看看如何生成唯一hash码

static inline intptr_t get_next_hash(Thread* self, oop obj) {
  intptr_t value = 0;
  if (hashCode == 0) {
    // This form uses global Park-Miller RNG.
    // On MP system we'll have lots of RW access to a global, so the
    // mechanism induces lots of coherency traffic.
    value = os::random();
  } 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 == 2) {
    value = 1;            // for sensitivity testing
  } else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
  } else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
  } 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;
  }

  value &= markWord::hash_mask;
  if (value == 0) value = 0xBAD;
  assert(value != markWord::no_hash, "invariant");
  return value;
}
0. A randomly generated number.随机数
1. A function of memory address of the object.内存地址函数
2. A hardcoded 1 (used for sensitivity testing.)硬编码为数字1
3. A sequence.自增序列
4. The memory address of the object, cast to int.内存地址强转为int
5. Thread state combined with xorshift (https://en.wikipedia.org/wiki/Xorshift)线程状态联合xorshift

根据src/share/vm/runtime/globals.hpp中,生产环境是5,也就是xorshift,应该也是一个随机数方案

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

openjdk8和9使用的是5,openjdk7和6使用的是第一种方案(也就是随机数方案)。

对象头与同步

在openjdk中,mark word的描述如下:细节看这里

30 // The markOop describes the header of an object.
31 //
32 // Note that the mark is not a real oop but just a word.
33 // It is placed in the oop hierarchy for historical reasons.
34 //
35 // Bit-format of an object header (most significant first, big endian layout below):
36 //
37 //  32 bits:
38 //  --------
39 //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
40 //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
41 //             size:32 ------------------------------------------>| (CMS free block)
42 //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
43 //
44 //  64 bits:
45 //  --------
46 //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
47 //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
48 //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
49 //  size:64 ----------------------------------------------------->| (CMS free block)
50 //
51 //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
52 //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
53 //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
54 //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

mark word格式在32和64位略有不同。后者有两个变体,具体取决于是否启用了压缩对象指针。默认情况下,Oracle和OpenJDK 8都执行。 如果对象处于偏向锁定状态,那么有23bit存储的是偏向线程的指针,那么从哪里取唯一hash码呢?

偏向锁

对象的偏向状态是偏向锁导致的。从hotspot6开始尝试减少给一个对象加锁的成本。这些操作很昂贵,因为它们的实现通常依赖于原子CPU指令(CAS),以便在不同线程上安全地处理对象上的锁定/解锁请求。但是根据分析,在大多数应用中,大部分的对象只会被一个线程锁定,所以上述原子指令的执行是一种浪费(cas指令已经很快了,比上下文切换快多了,也是一种浪费。。。),为了避免这种浪费,有偏向锁定的JVM允许线程让对象偏向自己。如果一个对象是偏心的,那个幸运的线程加锁和解锁连cas指令都不需要执行,只有没有多个线程争取同一个对象,偏向锁的性能会很好。 继续看FastHashCode:

601 intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
602   if (UseBiasedLocking) {
610     if (obj->mark()->has_bias_pattern()) {
          ...
617       BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
          ...
619       assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
620     }
621   }

生成唯一hash码时,会撤销已存在的偏向,并且会禁用此对象的偏向能力(false意味着不要尝试重偏向),上述代码几行之后,这个确实是不变的:

637   // object should remain ineligible for biased locking
638   assert (!mark->has_bias_pattern(), "invariant") ;

这意味着请求一个对象的唯一hash码会禁用这个对象的偏向锁,尝试锁定此对象需要使用昂贵的原子指令,即使只有一个线程请求锁。

为什么偏向锁和唯一hash码有冲突?

要回答这个问题,我们必须了解哪些是标记字的可能位置,具体取决于对象的锁定状态。从HotSpot Wiki的示例图中有如下转换: 如何实现译文jdk默认hashCode方法 

到此,关于“如何实现译文jdk默认hashCode方法”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI