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 中删除了令人困惑的地址计算提及的原因之一。
相关文章:
JVM常用概念之身份哈希码
问题 当我们调用Object.hashCode时,如果没有用户没有提供哈希码,会发生什么? System.identityHashCode如何工作?它是否获取对象地址? 基础知识 在 Java 中,每个对象都有equals和hashCode ,即…...
vue 对接 paypal 订阅和支付
一个是支付一个是订阅,写的时候尝试把他们放到一个里面,但是会报错,所以分开写了 我们的页面,前三个为订阅最后一个是支付,我把他们放到一个数组里面循环展示的,所以我们判断的时候只要判断id是否为4&#…...
Spring Boot - 动态编译 Java 类并实现热加载
为什么需要动态编译? 想象这样一个场景:你的系统需要实时更新业务规则,但重启服务会导致用户体验中断;或者你正在开发一款低代码平台,允许用户编写自定义逻辑并即时生效。这时,动态编译并加载 Java 类的能…...
基于javaweb的SpringBoot实习管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
流影---开源网络流量分析平台(一)(小白超详细)
目录 流影介绍 一、技术架构与核心技术 二、核心功能与特性 流影部署 流影介绍 一、技术架构与核心技术 模块化引擎设计 流影采用四层模块化架构:流量探针(数据采集)、网络行为分析引擎(特征提取)、威胁检测引擎&…...
Spring Boot事件机制详解
Spring Boot事件机制详解 1. 事件机制基础 1.1 什么是事件驱动架构 事件驱动架构(Event-Driven Architecture, EDA)是一种软件设计模式,其中系统组件通过事件的发布与订阅进行通信。在Spring Boot中,事件机制为应用程序提供了松耦合的组件间通信方式&…...
【商城实战(63)】配送区域与运费设置全解析
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
2025高频面试算法总结篇【字符串】
文章目录 直接刷题链接直达无重复字符的最长子串给定一个数,删除K位得到最小值至多包含 K 个不同字符的最长子串字符串的排列至少有K个重复字符的最长子串 直接刷题链接直达 如何找出一个字符串中的最大不重复子串 3. 无重复字符的最长子串 给定一个数࿰…...
Python散点密度图(Scatter Density Plot):数据可视化的强大工具
在数据驱动决策的时代,能够高效地处理和可视化多变量数据是一项 crucial 的技能。今天,我们就来深入探讨散点密度图(Scatter Density Plot),这是一种将散点图和核密度估计相结合的数据可视化技术,主要用于展示大量数据点在二维平面上的分布情况。 一、散点密度图的特点 …...
Oracle 数据库安全评估(DBSAT)简明过程
下载DBSAT 从这里下载。 实际是从MOS中下载,即:Oracle Database Security Assessment Tool (DBSAT) (Doc ID 2138254.1)。 最新版本为3.1.0 (July 2024),名为dbsat.zip,近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 新兴的大规模文本到图像生成模型,如稳定扩散(SD),已…...
【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)
基于 Spring Boot uniapp 的企业培训打卡小程序设计与实现 系统功能结构图如下: 一、课题背景 在当今快节奏的商业环境中,企业培训对于员工的成长和企业的发展至关重要。为了满足企业对高效培训管理和员工便捷学习的需求,基于 Spring Boot …...
腾讯面经,有点难度~
今天分享组织内的朋友在腾讯安全的实习面经。 内容涵盖了QPS测试方法、SQL聚合查询、Linux进程管理、Redis数据结构与持久化、NAT原理、Docker隔离机制、Go语言GMP调度模型、协程控制、系统调用流程、变量逃逸分析及map操作等等知识点。 下面是我整理的面经详解: …...
LeetCode(704):二分查找
二分查找 题目链接 题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 #include<stdio.h> //左闭…...
探索AI的无限可能,体验智能对话的未来,大模型 API 演示
探索AI的无限可能,体验智能对话的未来,大模型 API 演示 效果展示: 项目概述 这是一个基于 Vue 3 TypeScript Vite 构建的 Vista AI 演示项目,旨在提供一个简洁易用的界面来展示 Vista AI 大语言模型的能力。项目包含 API 演示…...
26考研——图_图的存储(6)
408答疑 文章目录 二、图的存储图的存储相关概念邻接矩阵存储方式邻接矩阵的定义顶点的度计算邻接矩阵的特点邻接矩阵的局限性 应用场景邻接矩阵的幂次意义(了解即可) 邻接表存储方式邻接表定义邻接表结构邻接表的特点 邻接矩阵和邻接表的适用性差异十字…...
Spark读取文件系统的数据(sbt打包测试)-入门级别Demo
学习目标 通过本关卡练习,您将学到: 如何使用Spark访问本地文件和HDFS文件Spark应用程序的编写、编译和运行方法 相关知识 操作系统:Ubuntu 16.04; Spark版本:2.4.0; Hadoop版本:3.1.3。 编…...
5.1 位运算专题:LeetCode 面试题 01.01. 判定字符是否唯一
1. 题目链接 LeetCode 面试题 01.01. 判定字符是否唯一 2. 题目描述 实现一个算法,确定一个字符串的所有字符是否全部唯一(即没有重复字符)。要求如下: 不使用额外的数据结构(如哈希表)字符串仅包含小写…...
datawhale组队学习--大语言模型—task4:Transformer架构及详细配置
第五章 模型架构 在前述章节中已经对预训练数据的准备流程(第 4 章)进行了介绍。本章主 要讨论大语言模型的模型架构选择,主要围绕 Transformer 模型(第 5.1 节)、详细 配置(第 5.2 节)、主流架…...
Python虚拟环境:从入门到实战指南
目录 一、为什么需要Python虚拟环境? 二、如何创建Python虚拟环境? 1. 使用venv(Python 3.3内置) 2. 使用virtualenv(第三方工具) 3. 使用conda(适合数据科学项目) 三、虚拟环…...
如何提升 Java 开发能力?
如何提升 Java 开发能力? 要系统提升 Java 开发能力,需从 基础巩固、技术拓展、实战经验、持续学习 四个维度入手。以下是详细的进阶路径和具体建议: 一、夯实 Java 核心基础 深入理解语言特性 必学内容: JVM 原理:…...
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型同步与异步同步异步对比同步 I/O 的缺点异步 I/O 的优点 理解异步通知 I/O 模型实现异步通知 I/O 模型WSAEventSelect 函数和通知…...
Qt6相对Qt5的主要提升(AI总结)
我: Qt 6 相对于5 有哪些新功能? Qt 6 相对于 Qt 5 有诸多新功能和改进,以下是主要的新增特性: 1. 架构和核心库的重构 模块化设计:Qt 6 采用了更加灵活的模块化设计,开发者可以按需引入必要的功能模块&a…...
消息队列ActiveMQ、RabbitMQ、RocketMQ、Kafka对比分析和选型
ActiveMQ、RabbitMQ、RocketMQ、Kafka对比分析和选型 四大消息队列详细对比 1. ActiveMQ 核心特性: 基于JMS规范,支持多种协议(AMQP、STOPP、MQTT等)。提供主从架构(Master-Slave)和共享存储集群。支持持…...
2025:sql注入详细介绍
先说一个阿里云学生无门槛免费领一年2核4g服务器的方法: 阿里云服务器学生无门槛免费领一年2核4g_阿里云学生认证免费服务器-CSDN博客 SQL注入(SQL Injection)是一种常见的网络安全漏洞,攻击者通过在应用程序的输入参数中注入恶意…...
MyBatis操作数据库进阶——动态SQL
动态 SQL 是根据程序运行时的条件灵活生成不同 SQL 语句的技术。它的核心目的是在不修改代码 的前提下,通过条件判断、循环等逻辑,动态拼接 SQL 片段,解决传统 SQL 语句死板、难以应对复杂业务场景的问题。 一、<if> 标签 先来观…...
使用LLama-Factory的简易教程(Llama3微调案例+详细步骤)
引言:一套快速实现 Llama3 中文微调的教程 主要参考:胖虎遛二狗的 B 站教学视频《【大模型微调】使用Llama Factory实现中文llama3微调》 ✅ 笔者简介:Wang Linyong,西工大,2023级,计算机技术 研究方向&am…...
LabVIEW发电平台数据采集系统
本文详细介绍了基于LabVIEW的摇臂式波浪发电平台数据采集系统的设计与实现。通过整合LabVIEW软件与多种传感器技术,本系统能够有效提升数据采集的准确性和效率,为波浪能的利用和发电设备的优化提供科学依据。 项目背景 随着全球能源需求增长和环境保…...
气象可视化卫星云图的方式:方法与架构详解
气象卫星云图是气象预报和气候研究的重要数据来源。通过可视化技术,我们可以将卫星云图数据转化为直观的图像或动画,帮助用户更好地理解气象变化。本文将详细介绍卫星云图可视化的方法、架构和代码实现。 一、卫星云图可视化方法 1. 数据获取与预处理 卫星云图数据通常来源…...
abaqus 二次开发 No module named ‘abaqusConstants
在 Python 中遇到 “No module named ‘abaqusConstants’” 错误通常意味着 Python 无法找到名为 abaqusConstants 的模块。这可能是由以下几个原因造成的: 拼写错误:首先确认模块名是否正确。通常在 Abaqus 的 Python 环境中,正确的模块名…...
