JVM常用概念之身份哈希码

问题

当我们调用Object.hashCode时,如果没有用户没有提供哈希码,会发生什么? System.identityHashCode如何工作?它是否获取对象地址?

基础知识

在 Java 中,每个对象都有equals和hashCode ,即使用户不提供。如果用户不提供equals的覆盖,则使用== (identity) 比较。如果用户不提供hashCode的覆盖,则使用System.identityHashCode执行哈希码计算。

Object.hashCode的Javadoc说明,hashCode 的一般约定是:

  • 在 Java 应用程序执行期间,如果对同一对象多次调用 hashCode 方法,则该方法必须始终返回相同的整数,前提是对象上用于 equals 比较的信息未发生修改。此整数不必在应用程序的一次执行和同一应用程序的另一次执行之间保持一致。
  • 如果根据 equals(Object) 方法两个对象相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
  • 如果两个对象根据 equals(java.lang.Object) 方法不相等,则不要求对这两个对象分别调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,对不相等的对象产生不同的整数结果可能会提高哈希表的性能。

在合理实用的情况下,Object 类定义的 hashCode 方法会为不同的对象返回不同的整数。(这通常是通过将对象的内部地址转换为整数来实现的,但 Java™ 编程语言并不要求采用这种实现技术。)

哈希码应该具有两个属性:a)分布性好,即不同对象的哈希码尽可能不同;b)幂等性,即具有相同关键对象组件的对象具有相同的哈希码。请注意,后者意味着如果对象没有更改这些关键对象组件,则其哈希码也不应该更改。

更改对象的方式经常会导致错误,即其hashCode在使用后发生变化。例如,将对象作为键添加到HashMap ,然后更改其字段,使 hashCode 也发生变化,这会导致令人惊讶的行为:可能根本无法在映射中找到该对象,因为内部实现会在“错误”的存储桶中查找。同样,哈希码分布不均(例如返回常量值)也经常会导致性能异常。

对于用户指定的哈希码,这两个属性都是通过对用户选择的字段集进行计算来实现的。如果字段和字段值足够多样化,它将分布良好,并且通过在未更改的(例如 final)字段上进行计算,我们可以获得幂等性。在这种情况下,我们不需要将哈希码存储在任何地方。一些哈希码实现可能会选择将其缓存在另一个字段中,但这不是必需的。

对于身份哈希码,不能保证有字段可用于计算哈希码,即使有,我们也不知道这些字段实际上有多稳定。考虑没有字段的java.lang.Object :它的哈希码是什么?两个分配的Object几乎是彼此的镜像:它们具有相同的元数据,具有相同的(即空的)内容。它们唯一不同之处在于它们分配的地址,但即便如此也存在两个问题。首先,地址的熵非常低,特别是来自大多数 Java GC 所采用的 bump-ptr 分配器时,因此分布不太好。其次,GC 会移动对象,因此地址不是幂等的。 从性能角度来看,返回常量值是行不通的。

因此,当前的实现从内部 PRNG(“良好分布”)计算身份哈希码,并将其存储为每个对象(“幂等性”)。

为了实现这一点,Hotspot JVM 有几种不同风格的身份哈希码生成器,它将计算出的身份哈希码存储在对象头中以保证稳定性。身份哈希码生成器的选择直接影响hashCode本身和hashCode用户的性能,尤其是java.util.HashMap 。将计算出的身份哈希码存储在对象头中的实现选择直接影响哈希码的准确性(我们可以存储多少位)以及与对象头其他用户的复杂交互。

Hotspot 代码库中有一个地方生成了哈希码,代码如下:

static inline intptr_t get_next_hash(Thread* current, oop obj) {...if (hashCode == 0) {// Use os::random();} else if (hashCode == 1) {// Use address with some mangling} else if (hashCode == 2) {// Use constant 1} else if (hashCode == 3) {// Use global counter} else if (hashCode == 4) {// Use raw address} else {// Use thread-local PRNG}...
}

该设置可作为-XX:hashCode VM 选项访问。生成的哈希码稍后将被安装到 ObjectSynchronizer::FastHashCode 中的对象头中,并在下一次哈希码请求中重用。

哈希码存储

我们可以使用JOL查看身份哈希码存储。实际上,有一个特定的示例已经捕获了我们想要的内容:

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;import static java.lang.System.out;/*** @author Aleksey Shipilev*/
public class JOLSample_15_IdentityHashCode {/** The example for identity hash code.** The identity hash code, once computed, should stay the same.* HotSpot opts to store the hash code in the mark word as well.* You can clearly see the hash code bytes in the header once* it was computed.*/public static void main(String[] args) {out.println(VM.current().details());final A a = new A();ClassLayout layout = ClassLayout.parseInstance(a);out.println("**** Fresh object");out.println(layout.toPrintable());out.println("hashCode: " + Integer.toHexString(a.hashCode()));out.println();out.println("**** After identityHashCode()");out.println(layout.toPrintable());}public static class A {// no fields}}
$ java -cp jol-samples/target/jol-samples.jar org.openjdk.jol.samples.JOLSample_15_IdentityHashCode
...**** Fresh object
org.openjdk.jol.samples.JOLSample_15_IdentityHashCode$A object internals:
OFF  SZ  DESCRIPTION             VALUE0   8  (object header: mark)   0x0000000000000001 (non-biasable; age: 0)8   4  (object header: class)  0x00cc400012   4  (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalhashCode: 4e9ba398**** After identityHashCode()
org.openjdk.jol.samples.JOLSample_15_IdentityHashCode$A object internals:
OFF  SZ  DESCRIPTION             VALUE0   8  (object header: mark)   0x0000004e9ba39801 (hash: 0x4e9ba398; age: 0)8   4  (object header: class)  0x00cc400012   4  (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

在这里,内部生成器算出此对象的哈希码是4e9ba398 ,并将其记录在对象头中。 每次后续调用身份哈希码时,现在都会重用此值。

哈希码生成器随机性

为了估计身份哈希码生成器的随机性,我们可以使用如下测试:

public class HashCodeValues {static long sink;public static void main(String... args) {for (int t = 0; t < 100000; t++) {for (int c = 0; c < 1000; c++) {sink = new Object().hashCode();}System.out.println(new Object().hashCode());}}
}

此测试的目标是打印连续对象的标识哈希码。它伴随着演示问题:一些生成器的分布非常糟糕,因此在大型图形规模上它们彼此难以区分。因此,测试跳过打印大多数中间对象的哈希码,同时仍然(尴尬地)确保计算哈希码。

哈希码值的热图将是这样的:

在这里插入图片描述
请注意以下几点:

  • 这两个 PRNG 的表观值域几乎占所有可能哈希码值的一半。值中只有“上”半部分存在,因为在 64 位 JVM 中,只有身份哈希码的前 31 位存储在标头中。
  • 对象地址的熵非常低。这是由于(T)LAB 分配的线性特性:时间相邻的对象将具有非常相似的地址。事实上,这就是为什么从对象地址生成哈希码是一个坏主意!
  • 全局计数器的分布很不方便。全局计数器的值域仅仅是我们曾经计算过哈希码的对象的数量。
  • 常量哈希码表现出极其糟糕的分布。

对于基于地址和全局计数器的哈希码,经常被忽视的一点是——虽然它们可能比 PRNG 更独特(PRNG 还会遭受生日悖论)——但它们的位相关性非常好,一旦您从哈希码中选择非低位子运行,就会面临发生子哈希冲突的风险。此外,当我们以常规模式处理元素时,常规哈希码(如全局计数器哈希码)的性能会很奇怪,例如,在哈希表中保留每隔一个对象会很快导致元素只有奇数/偶数哈希码,这会未充分利用哈希表,例如执行hashcode % size存储桶放置。

哈希码生成器性能

看看这些生成器的性能可能会很有趣。在像这样的简单 JMH 基准测试中,您或多或少会得到可预测的结果:

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@Threads(Threads.MAX)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
public class IdentityHashCode {Object o = new Object();@Benchmarkpublic void cold(Blackhole bh) {Object lo = new Object();bh.consume(lo);bh.consume(lo.hashCode());}@Benchmarkpublic void warm(Blackhole bh) {Object lo = o;bh.consume(lo); // for symmetrybh.consume(lo.hashCode());}
}

在搭载最新 JDK 17 EA 的运行环境上,其运行效果如下:

Benchmark              Mode  Cnt    Score   Error  Units# Style 0: os::random() PRNG
IdentityHashCode.cold  avgt   15  400.703 ± 12.470  ns/op
IdentityHashCode.warm  avgt   15    5.051 ±  0.064  ns/op# Style 1: STW Address
IdentityHashCode.cold  avgt   15   86.180 ±  1.854  ns/op
IdentityHashCode.warm  avgt   15    5.109 ±  0.074  ns/op# Style 2: Constant 1
IdentityHashCode.cold  avgt   15   83.195 ±  2.034  ns/op
IdentityHashCode.warm  avgt   15    5.045 ±  0.060  ns/op# Style 3: Global Counter
IdentityHashCode.cold  avgt   15  124.748 ±  0.946  ns/op
IdentityHashCode.warm  avgt   15    5.069 ±  0.079  ns/op# Style 4: Address
IdentityHashCode.cold  avgt   15   86.232 ±  2.984  ns/op
IdentityHashCode.warm  avgt   15    5.066 ±  0.058  ns/op# Style 5: MT PRNG
IdentityHashCode.cold  avgt   15   90.809 ±  0.792  ns/op
IdentityHashCode.warm  avgt   15    5.087 ±  0.077  ns/op

请注意以下几点:

  • 无论使用哪种生成器, warm变体的表现都相同。这是有道理的,因为该路径仅拾取已存储的身份哈希码。
  • 大部分cold成本都花在了 VM 的哈希码计算上。即便是最基本的生成器(返回常数 1)的成本也相当高。
  • 其他生成器的效果会滚雪球般增长。值得注意的是,os::random() PRNG 会将原子更新为 PRNG 状态,因此存在严重的可扩展性问题。

总结

身份哈希码生成器的选择在很大程度上取决于具体实现。生成器应具有良好的分布性和高度可扩展性。这就是为什么现代 Hotspot VM 默认使用hashCode=5 (多线程 PRNG)。

身份哈希码计算根本不涉及地址计算。这也是为什么最终从 Javadoc 中删除了令人困惑的地址计算提及的原因之一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/37679.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Compose 实践与探索十六 —— 与传统的 View 系统混用

Compose 发展初期的几年&#xff0c;会是新的模块用 Compose 写&#xff0c;然后逐渐的把老界面从 View 替换成 Compose 组件&#xff0c;直到全部或几乎全部是 Compose 代码的模式。 原生的 SurfaceView 与 TextureView 的重点是在它们底层的 Surface API&#xff0c;而不是 V…

[7-01-03].SpringBoot3集成MinIo

MinIO学习大纲 一、Spingboot整合MinIo 第1步&#xff1a;搭建SpringBoot项目&#xff1a; 第2步&#xff1a;引入minio依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi&q…

xLua_001 Lua 文件加载

xLua下载 1、HelloWrold 代码 using System.Collections; using System.Collections.Generic; using UnityEngine; using XLua; // 引入XLua命名空间 public class Helloworld01 : MonoBehaviour {//声明LuaEnv对象 private LuaEnv luaenv;void Start(){//实例化LuaEnv对象…

Redis 管道(Pipeline)深度解析:原理、场景与实战

一、为什么需要管道? 1. Redis 的请求/响应模式瓶颈 在传统 Redis 交互中: 客户端:发送命令1 → 等待响应 → 发送命令2 → 等待响应... 每次操作都需要经历: 网络传输时间 服务器处理时间 客户端阻塞等待时间 性能损耗:当需要执行大量命令时,频繁的网络往返(RTT)成…

OSPF-8 OSPF特殊区域NSSA

上一期我们介绍了特殊区域Stub区域,描述了Stub区域是如何来缩减LSA的数量以及下发LSA路由信息的 但由于Stub以及Stub区域虽然达到了缩减LSA数量的目的,但是无法引入外部路由信息所以这章我们来介绍OSPF的NSSA特殊区域,来看看NSSA区域与Stub区域有什么区别 一、概述 Stub区域与…

upload-labs靶场学习记录2

pass-2 打开靶场 提示这关与mime进行检查。 所以我们上传1.php文件并且抓包。 抓到包&#xff0c;我们修改对应的Content-Typed的类型为image/jpeg来伪造数据&#xff0c;让后端以为我们传入的是一个图片文件。 点击发送查看回传&#xff0c;复制图片的链接。 打开蚁剑&…

【C++】多态

目录 文章目录 前言 一、多态的概念 二、多态的定义及实现 三、重载/重写/隐藏的对比 四、纯虚函数和抽象类 五、多态的原理 总结 前言 本文主要讲述C中的多态&#xff0c;涉及的概念有虚函数、协变、纯虚函数、抽象类、虚表指针和虚函数表等。 一、多态的概念 多态分…

k8s部署prometheus+alertmanager+grafana监控

1、下载prometheus.yaml文件 根据github上面的版本对应说明&#xff0c;选择我们要下载的版本&#xff0c;github地址 rootiZj6c72dzbei17o2cuksmeZ:~# wget https://github.com/prometheus-operator/kube-prometheus/archive/refs/tags/v0.14.0.tar.gz rootiZj6c72dzbei17o2cu…

扩展卡尔曼滤波

1.非线性系统的线性化 标准卡尔曼滤波 适用于线性化系统&#xff0c;扩展卡尔曼滤波 则扩展到了非线性系统&#xff0c;核心原理就是将非线性系统线性化&#xff0c;主要用的的知识点是 泰勒展开&#xff08;我另外一篇文章的链接&#xff09;&#xff0c;如下是泰勒展开的公式…

【从0实现muduo库系列】第二讲:基础类型与工具类

0 章节重点 重点内容 视频讲解&#xff1a;《CLinux编程进阶&#xff1a;从0实现muduo C网络框架系列》-第2讲.基础类型与工具类 代码改动 cp -r lesson1 lesson2 实现&#xff1a;base/Types.h 实现&#xff1a;base/copyable.h和noncopyable.h 实现&#xff1a;base/Str…

Qemu-STM32(十):STM32F103开篇

简介 本系列博客主要描述了STM32F103的qemu模拟器实现&#xff0c;进行该项目的原因有两点: 作者在高铁上&#xff0c;想在STM32F103上验证一个软件框架时&#xff0c;如果此时掏出开发板&#xff0c;然后接一堆的线&#xff0c;旁边的人估计会投来异样的目光&#xff0c;特别…

鸿蒙HarmonyOS NEXT应用崩溃分析及修复

鸿蒙HarmonyOS NEXT应用崩溃分析及修复 如何保证应用的健壮性&#xff0c;其中一个指标就是看崩溃率&#xff0c;如何降低崩溃率&#xff0c;就需要知道存在哪些崩溃&#xff0c;然后对症下药&#xff0c;解决崩溃。那么鸿蒙应用中存在哪些崩溃类型呢&#xff1f;又改如何解决…

K8S-etcd服务无法启动问题排查

一、环境、版本信息说明 k8s&#xff1a;v1.19.16 etcdctl version: 3.5.1 3台etcd&#xff08;10.xxx.xx.129、10.xxx.xx.130、10.xxx.xx.131&#xff09;组成的集群。 二、问题根因 129节点的etcd数据与其他两台数据不一致&#xff0c;集群一致性校验出错导致无法加入集…

【视觉提示学习】3.21论文随想

. . Frontiers of Information Technology & Electronic Engineering. 2024, 25(1): 42-63 https://doi.org/10.1631/FITEE.2300389 中文综述&#xff0c;根据里面的架构&#xff0c;把视觉提示学习分成两类&#xff0c;一类是单模态提示学习&#xff08;以vit为代表&…

基于SpringBoot的“校园招聘网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园招聘网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…

爱普生晶振FC2012AA汽车ADAS主控制系统的理想选择

在汽车智能化的浪潮中&#xff0c;先进驾驶辅助系统&#xff08;ADAS&#xff09;正迅速成为现代汽车的核心技术之一。ADAS 系统通过集成多种传感器、摄像头和高性能芯片&#xff0c;实现对车辆周围环境的实时监测和智能决策&#xff0c;为驾驶者提供全方位的安全保障。而在这一…

基于 ABAP RESTful 应用程序编程模型开发 OData V4 服务

一、概念 以个人图书管理为例&#xff0c;创建一个ABAP RESTful 应用程序编程模型项目。最终要实现的效果&#xff1a; 用于管理书籍的程序。读取、修改和删除书籍。 二、Data Model-数据模型 2.1 创建项目基础数据库表 首先&#xff0c;创建一个图书相关的表&#xff0c;点…

阿里云平台服务器操作以及发布静态项目

目录&#xff1a; 1、云服务器介绍2、云服务器界面3、发布静态项目1、启动nginx2、ngixn访问3、外网访问测试4、拷贝静态资源到nginx目录下并重启nginx 1、云服务器介绍 2、云服务器界面 实例详情&#xff1a;里面主要显示云服务的内外网地址以及一些启动/停止的操作。监控&…

注意力机制,本质上是在做什么?

本文以自注意机制为例&#xff0c;输入一个4*4的矩阵 如下&#xff1a; input_datatorch.tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ],dtypetorch.float) 得到Q和K的转置如下。 此时&#xff0c;计算QK^T ,得到如下结果 第一行第一个位置就是第一条样本和第…

C语言-数组指针和指针数组

指针 数组指针与指针数组 数组指针 定义 概念&#xff1a;数组指针是指向数组的指针&#xff0c;本质上还是指针 特点&#xff1a; ①先有数组&#xff0c;后有指针 ②它指向的是一个完整的数组 一维数组指针 语法&#xff1a; 数据类型 (*指针变量名)[容量]; 案例&a…