java object多大 java对象内存模型 数组有多长(六)identityhashcode究竟是什么?内存地址?

https://cloud.tencent.com/developer/article/1622192

java默认的hashcode方法到底得到的是什么?

 

在hashCode方法注释中,说hashCode一般是通过对象内存地址映射过来的。

As much as is reasonably practical, the hashCode method defined by class {@code Object} does return distinct integers for distinct objects.

(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

我们推测,很有可能是在第一次调用hashCode方法时获取当前内存地址,并将其保存在对象的某个地方,当下次调用时,只用从对象的某个地方获取值即可。但这样实际是有问题的,你想想,如果对象被归集到别的内存上了,那在对象以前的内存上创建的新对象其hashCode方法返回的值岂不是和旧对象的一样了?这倒没关系,java规范允许这样做。

hashCode方法的实现依赖于jvm,不同的jvm有不同的实现

我们可能会认为 ObjectSynchronizer :: FastHashCode() 会判断当前的hash值是否为0,如果是0则生成一个新的hash值。实际上没那么简单,来看看其中的代码。

上边的片段展示了hash值是如何生成的,可以看到hash值是存放在对象头中的,如果hash值不存在,则使用get_next_hash方法生成。

在第二节中,我们终于找到了生成hash的最终函数 get_next_hash,这个函数提供了6种生成hash值的方法。

0. A randomly generated number.

1. A function of memory address of the object.

2. A hardcoded 1 (used for sensitivity testing.)

3. A sequence.

4. The memory address of the object, cast to int.

5. Thread state combined with xorshift (https://en.wikipedia.org/wiki/Xorshift)

OpenJDK8默认采用第五种方法。而 OpenJDK7OpenJDK6 都是使用第一种方法,即 随机数生成器

大家也看到了,JDK的注释算是欺骗了我们,明明在678版本上都是随机生成的值,为什么要引导说是内存地址映射呢?我理解可能以前就是通过第4种方法实现的。

normal object和biased object分别存放的是hashcode和java的线程id。因此也就是说如果调用了本地方法hashCode,就会占用偏向锁对象使用的位置,偏向锁将会失效,晋升为轻量级锁。

当未锁定时,线程ID为0,第一次获取锁时,线程会把自己的线程ID写到ThreadID字段内,这样,下一次获取锁时直接检查标记字中的线程ID和自身ID是否一致,如果一致就认为获取了锁,因此不需要再次获取锁。(重入)

  • OpenJDK默认的hashCode方法实现和对象内存地址无关,在版本6和7中,它是随机生成的数字,在版本8中,它是基于线程状态的数字。(AZUL-ZING的hashcode是基于地址的)
  • 在Hotspot中,hash值会存在标记字中。
  • hashCode方法和System.identityHashCode()会让对象不能使用偏向锁,所以如果想使用偏向锁,那就最好重写hashCode方法。)如果直接调用System.identityHashCode()还是会失效,只是当容器调用hashCode时可以避免
  • 如果大量对象跨线程使用,可以禁用偏向锁。
  • 使用-XX:hashCode=4来修改默认的hash方法实现。

 

 

 

 

 

 

https://www.jianshu.com/p/be943b4958f4

Java Object.hashCode()返回的是对象内存地址?

一直以为Java Object.hashCode()的结果就是通过对象的内存地址做相关运算得到的

先说结论:OpenJDK8 默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。和对象内存地址无关
// Load displaced header and check it has hash code
  mark = monitor->header();
  assert (mark->is_neutral(), "invariant") ;
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
...
}

对xorshift算法有兴趣可以参考原论文:https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
xorshift是由George Marsaglia发现的一类伪随机数生成器,其通过移位和与或计算,能够在计算机上以极快的速度生成伪随机数序列。
-XX:hashCode=4,改变默认的hashCode计算方式


 
 
 
https://blog.51cto.com/u_15082395/2590044
深度解析默认 hashCode() 的工作机制
 
众所周知,identity hash code使用了内存地址的整数表示。这也是J2SE JavaDocs中关于Object.hashCode()的描述:
 
...通常把对象内部地址
转换为整数,但这种实现不是 
Java™语言本身的要求。
 
这种实现似乎由问题,因为方法的定义要求:
 
执行Java程序时,在同一对象上多次调用
hashCode方法结果必须
返回相同的整数。
 
鉴于JVM会重定位对象(例如,在垃圾回收周期可能发生提升或压缩),因此在对象identity hash计算完成后需要能够确保不受重定位影响。
一种可能是首次调用hashCode()时获得对象当前内存位置,与对象一起保存在某个地方,比如对象头。
这样,即使对象被移到内存其它地方也会保留原来的哈希值。采用这种方法需要注意一点,可能产生两个对象具有相同的identity hash,但这是规范允许的。
 
如果存在id. hash(hash != 0),JVM将会返回。否则,将从get_next_hash中生成一个hash code。接着把存到ObjectMonitor置换过的对象头中。

为了让对象在重新定位后保持identity hash一致,需要把hash存入对象头。

获取identity hash的线程可能并不关心对象是否加锁但实际上这个操作会与锁机制使用相同的数据结构。这个过程异常复杂,不仅可能修改,还会移动(置换)对象头里的内容。

如果只有一个线程锁定对象,通过把锁定状态存入mark word,偏向锁可以不借助原子操作高效执行锁定与解锁操作。这里我也不是100%确定,但我知道其他线程也会请求identity hash,即使只有一个线程为对象加锁,对象头中的mark word可能也会发生争用,需要原子操作解决。偏向锁的存在就变得毫无意义。

至少在OpenJDK中,hashCode()的默认实现(identity hash code)与对象的内存地址无关。在OpenJDK 6和7中,它是一个随机生成的数字。在OpenJDK 8(现在是9)中,它是一个基于线程状态的数字。下面的测试给出了同样的结论。

下面的内容证明了“HashCode()依赖于JVM实现”:Azul Zing的确用对象内存地址生成的identity hash。

HotSpot的实现,仅生成一次identity hash并缓存在对象头的mark word中

Zing采用了不同的解决方案保持一致,即延迟存储id. hash,直到重定位完成再进行保存。在此之前,会把它存储在“pre-header”中。

在HotSpot中,调用默认hashCode()或者System.identityHashCode()会让对象不适合使用偏向锁。

这意味着,如果要在不发生争用的对象上进行同步,则最好覆盖默认hashCode()实现,否则JVM不会优化

在HotSpot中,可以针对对象禁用偏向锁。

非常有用。在实践中,我看到过很多竞争激烈的生产者消费者队列,偏向锁带来的麻烦比好处大得多。因此最后完全禁用了这个功能。事实证明,只要在指定对象或者类上调用System.identityHashCode()就可以解决。

 

posted on 2024-06-18 13:12  silyvin  阅读(1)  评论(0编辑  收藏  举报