JVM常用概念之身份哈希码

news2025/3/26 4:08:10

问题

当我们调用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             VALUE
  0   8  (object header: mark)   0x0000000000000001 (non-biasable; age: 0)
  8   4  (object header: class)  0x00cc4000
 12   4  (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

hashCode: 4e9ba398

**** After identityHashCode()
org.openjdk.jol.samples.JOLSample_15_IdentityHashCode$A object internals:
OFF  SZ  DESCRIPTION             VALUE
  0   8  (object header: mark)   0x0000004e9ba39801 (hash: 0x4e9ba398; age: 0)
  8   4  (object header: class)  0x00cc4000
 12   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();

    @Benchmark
    public void cold(Blackhole bh) {
        Object lo = new Object();
        bh.consume(lo);
        bh.consume(lo.hashCode());
    }

    @Benchmark
    public void warm(Blackhole bh) {
        Object lo = o;
        bh.consume(lo); // for symmetry
        bh.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.coloradmin.cn/o/2321710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue 对接 paypal 订阅和支付

一个是支付一个是订阅&#xff0c;写的时候尝试把他们放到一个里面&#xff0c;但是会报错&#xff0c;所以分开写了 我们的页面&#xff0c;前三个为订阅最后一个是支付&#xff0c;我把他们放到一个数组里面循环展示的&#xff0c;所以我们判断的时候只要判断id是否为4&#…

基于javaweb的SpringBoot实习管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

流影---开源网络流量分析平台(一)(小白超详细)

目录 流影介绍 一、技术架构与核心技术 二、核心功能与特性 流影部署 流影介绍 一、技术架构与核心技术 模块化引擎设计 流影采用四层模块化架构&#xff1a;流量探针&#xff08;数据采集&#xff09;、网络行为分析引擎&#xff08;特征提取&#xff09;、威胁检测引擎&…

Oracle 数据库安全评估(DBSAT)简明过程

下载DBSAT 从这里下载。 实际是从MOS中下载&#xff0c;即&#xff1a;Oracle Database Security Assessment Tool (DBSAT) (Doc ID 2138254.1)。 最新版本为3.1.0 (July 2024)&#xff0c;名为dbsat.zip&#xff0c;近45MB。 $ ls -lh dbsat.zip -rw-rw-r-- 1 oracle oins…

【T2I】Divide Bind Your Attention for Improved Generative Semantic Nursing

CODE: GitHub - boschresearch/Divide-and-Bind: Official implementation of "Divide & Bind Your Attention for Improved Generative Semantic Nursing" (BMVC 2023 Oral) ABSTRACT 新兴的大规模文本到图像生成模型&#xff0c;如稳定扩散(SD)&#xff0c;已…

【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)

基于 Spring Boot uniapp 的企业培训打卡小程序设计与实现 系统功能结构图如下&#xff1a; 一、课题背景 在当今快节奏的商业环境中&#xff0c;企业培训对于员工的成长和企业的发展至关重要。为了满足企业对高效培训管理和员工便捷学习的需求&#xff0c;基于 Spring Boot …

探索AI的无限可能,体验智能对话的未来,大模型 API 演示

探索AI的无限可能&#xff0c;体验智能对话的未来&#xff0c;大模型 API 演示 效果展示&#xff1a; 项目概述 这是一个基于 Vue 3 TypeScript Vite 构建的 Vista AI 演示项目&#xff0c;旨在提供一个简洁易用的界面来展示 Vista AI 大语言模型的能力。项目包含 API 演示…

26考研——图_图的存储(6)

408答疑 文章目录 二、图的存储图的存储相关概念邻接矩阵存储方式邻接矩阵的定义顶点的度计算邻接矩阵的特点邻接矩阵的局限性 应用场景邻接矩阵的幂次意义&#xff08;了解即可&#xff09; 邻接表存储方式邻接表定义邻接表结构邻接表的特点 邻接矩阵和邻接表的适用性差异十字…

datawhale组队学习--大语言模型—task4:Transformer架构及详细配置

第五章 模型架构 在前述章节中已经对预训练数据的准备流程&#xff08;第 4 章&#xff09;进行了介绍。本章主 要讨论大语言模型的模型架构选择&#xff0c;主要围绕 Transformer 模型&#xff08;第 5.1 节&#xff09;、详细 配置&#xff08;第 5.2 节&#xff09;、主流架…

《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型

《TCP/IP网络编程》学习笔记 | Chapter 21&#xff1a;异步通知 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 21&#xff1a;异步通知 I/O 模型同步与异步同步异步对比同步 I/O 的缺点异步 I/O 的优点 理解异步通知 I/O 模型实现异步通知 I/O 模型WSAEventSelect 函数和通知…

Qt6相对Qt5的主要提升(AI总结)

我&#xff1a; Qt 6 相对于5 有哪些新功能&#xff1f; Qt 6 相对于 Qt 5 有诸多新功能和改进&#xff0c;以下是主要的新增特性&#xff1a; 1. 架构和核心库的重构 模块化设计&#xff1a;Qt 6 采用了更加灵活的模块化设计&#xff0c;开发者可以按需引入必要的功能模块&a…

MyBatis操作数据库进阶——动态SQL

动态 SQL 是根据程序运行时的条件灵活生成不同 SQL 语句‌的技术。它的核心目的是在不修改代码‌ 的前提下&#xff0c;通过条件判断、循环等逻辑&#xff0c;动态拼接 SQL 片段&#xff0c;解决传统 SQL 语句死板、难以应对复杂业务场景的问题。 一、<if> 标签 先来观…

使用LLama-Factory的简易教程(Llama3微调案例+详细步骤)

引言&#xff1a;一套快速实现 Llama3 中文微调的教程 主要参考&#xff1a;胖虎遛二狗的 B 站教学视频《【大模型微调】使用Llama Factory实现中文llama3微调》 ✅ 笔者简介&#xff1a;Wang Linyong&#xff0c;西工大&#xff0c;2023级&#xff0c;计算机技术 研究方向&am…

LabVIEW发电平台数据采集系统

本文详细介绍了基于LabVIEW的摇臂式波浪发电平台数据采集系统的设计与实现。通过整合LabVIEW软件与多种传感器技术&#xff0c;本系统能够有效提升数据采集的准确性和效率&#xff0c;为波浪能的利用和发电设备的优化提供科学依据。 ​ 项目背景 随着全球能源需求增长和环境保…

气象可视化卫星云图的方式:方法与架构详解

气象卫星云图是气象预报和气候研究的重要数据来源。通过可视化技术,我们可以将卫星云图数据转化为直观的图像或动画,帮助用户更好地理解气象变化。本文将详细介绍卫星云图可视化的方法、架构和代码实现。 一、卫星云图可视化方法 1. 数据获取与预处理 卫星云图数据通常来源…

【蓝桥杯】每日练习 Day7

目录 前言 领导者 分析 代码 空调 分析 代码 面包店 分析 代码 前言 今天是第一部分的最后一天&#xff08;主打记忆恢复术和锻炼思维&#xff09;&#xff0c;从明天开始主播会逐步更新从位运算到dp问题的常见题型。 领导者&#xff08;分类讨论&#xff09; 分析 …

本地部署Stable Diffusion生成爆火的AI图片

直接上代码 Mapping("/send") Post public Object send(Body String promptBody) { JSONObject postSend new JSONObject(); System.out.println(promptBody); JSONObject body JSONObject.parseObject(promptBody); List<S…

从国家能源到浙江交通投资,全息技术在能源交通领域的创新应用

一、3D全息技术行业应用参数及设计制作要求 全息投影 全息投影技术通过激光器、全息片等设备&#xff0c;将物体的三维信息记录下来&#xff0c;并在特定条件下再现。应用参数包括投影距离、投影面积、投影亮度等。设计制作要求&#xff1a;高清晰度、高亮度、低噪音、稳定性好…

PageHiOffice网页组件(WebOffice文档控件)开发集成技巧专题一

PageHiOffice网页组件作为最新一代的WebOffice文档控件&#xff0c;这是目前市场上唯一能做到在Chrome等最新版浏览器中实现内嵌网页运行的商用文档控件&#xff0c;是OA及ERP等系统处理各种文档的福音。从发布到完善已经超过3年&#xff0c;不管是功能性还是稳定性都已经有了长…

本地安装deepseek大模型,并使用 python 调用

首先进入 ollama 官网 https://ollama.com/点击下载 下载完成后所有都是下一步&#xff0c;就可以 点击搜索 Models &#xff1a; https://ollama.com/search然后点击下载&#xff1a; 选择后复制: ollama run deepseek-r1:32b例如&#xff1a; 让它安装完成后&#xff1…