【Java对象】一文读懂 Java 对象庐山真面目及指针压缩
文章目录
- 版本及工具介绍
- Java 对象结构
- 对象头
- mark word 标记字
- mark word 标记字解析
- Lock Record
- class point 类元数据指针
- 实例数据
- 对齐填充
- 为什么需要对齐填充
- 常见 Java 数据类型对象分析
- ArrayList
- Long
- String
- Byte
- Boolean
- 其它
- 指针压缩
- 前置知识:32位操作系统为什么最多支持 4G 内存
- 从32位操作系统到64位操作系统
- 指针压缩:使用4字节指针的同时获得更大的内存
- 如何开启指针压缩
- 实现原理
- 思考
- mark word 数据字段为什么是不固定动态变化的
- mark word 是字段动态变化的,当获取锁时 hash code 等字段被存储在哪
- 个人简介
版本及工具介绍
- JDK版本:JDK 8
- Java 对象分析 Maven 插件
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version></dependency>
Java 对象结构
- 一个 Java 对象由三部分组成:对象头、实例数据、对齐数据,其中对象头分为 mark word 标记字和 class point 类元数据指针。
- jol-core 是 Java Object Layout(JOL)库的一部分,它是一个用于分析Java对象内存布局的工具。JOL 允许我们深入了解Java对象的内部结构,包括字段的偏移量、大小和布局,以及对象头的信息等。这对于性能优化和调试非常有用,特别是当我们需要了解对象在内存中的布局时。
- 如何使用 jol-core 打印Java对象信息
public class Test {static final A MUTEX = new A();public static void main(String[] args) {// 打印 JVM 信息System.out.println(VM.current().details());// hashCode 懒加载,调用 hashCode() 方法时生成存储在对象头System.out.println(MUTEX.hashCode());System.out.println(ClassLayout.parseInstance(MUTEX).toPrintable());synchronized (MUTEX) {System.out.println(ClassLayout.parseInstance(MUTEX).toPrintable());}System.out.println(ClassLayout.parseInstance(MUTEX).toPrintable());}
}class A {int a = 2;
}// 输出
# VM mode: 64 bits
# Compressed references (oops): 3-bit shift
# Compressed class pointers: 3-bit shift
# Object alignment: 8 bytes
# ref, bool, byte, char, shrt, int, flt, lng, dbl
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array base offsets: 16, 16, 16, 16, 16, 16, 16, 16, 161407343478 // 对象 hashCodeconcurrency.A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00000053e25b7601 (hash: 0x53e25b76; age: 0)8 4 (object header: class) 0xf800c14312 4 int A.a 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total// 64位JVM mark word 占用8字节
// 64位JVM class point 元数据指针占用4字节(正常应该占用8字节,这里开启了指针压缩)
// 实例数据 int字段占用4字节
// 共计 16 字节 默认8字节对齐,不需要补齐concurrency.A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00000096d75ff7e8 (thin lock: 0x00000096d75ff7e8)8 4 (object header: class) 0xf800c14312 4 int A.a 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes totalconcurrency.A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00000053e25b7601 (hash: 0x53e25b76; age: 0)8 4 (object header: class) 0xf800c14312 4 int A.a 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
对象头
- 对象头由 mark word 标记字和 class point 类元数据指针两部分组成。
mark word 标记字
- mark word 记录了 Java 对象运行时的数据信息,如持有的锁、是否是偏向锁、锁持有线程、hashcode、分代年龄等等,32位JVM中占用4个字节,64位JVM中占用8个字节,具体字段如下所示:
mark word 标记字解析
补充知识:
大端存储(Big-Endian):数据的高字节存储在低地址中,数据的低字节存储在高地址中
小端存储(Little-Endian):数据的高字节存储在高地址中,数据的低字节存储在低地址中// 上文示例 Mark word 分析 JVM 64位
0x00000053e25b7601 (hash: 0x53e25b76; age: 0)十六进制数: 0x00000053e25b7601
二进制数: 0000 0000 0000 0000 0000 0000 0101 00111110 0010 0101 1011 0111 0110 0000 0001锁标记: 01 无锁
分代年龄:0000 age:0
hashCode: 101 0011 1110 0010 0101 1011 0111 0110 = hash: 0x53e25b76 = 十进制:14073434780x00000096d75ff7e8 (thin lock: 0x00000096d75ff7e8)十六进制数: 0x00000096d75ff7e8
二进制数: 0000 0000 0000 0000 0000 0000 1001 01101101 0111 0101 1111 1111 0111 1110 1000锁标记: 00 轻量级锁
指向线程堆栈Lock Record指针:
0000 0000 0000 0000 0000 0000 1001 0110 1101 0111 0101 1111 1111 0111 1110 10
Lock Record
- lock record 保存对象 mark word 的原始值,还包含识别哪个对象被锁的所必需的元数据。
class point 类元数据指针
- class point 类元数据指针指向方法区的instanceKlass实例(虚拟机根据该指针确认对象是哪个类的实例),32位JVM中占用4个字节,64位JVM中占用8个字节或4个字节(指针压缩)。
实例数据
- 存储对象的字段信息。(包括继承的字段)
对齐填充
- Java 对象的大小默认8字节对齐,当大小不为8的倍数时,需要进行对齐填充,如:14字节需要填充为16字节。
为什么需要对齐填充
- 对齐填充是一种以空间换时间的方案,可以提高内存的访问效率,本质是为了更加高效的利用缓存行。
示例:
CPU缓存行(Cache Line)是计算机处理器缓存的最小存储单位,一般来说,32 位系统一般为 4字节、64位系统一般为 8字节。
- 指针压缩技术也依赖 Java 对象字节对齐。
常见 Java 数据类型对象分析
ArrayList
java.util.ArrayList object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf8002f3912 4 int AbstractList.modCount 316 4 int ArrayList.size 320 4 java.lang.Object[] ArrayList.elementData [(object), (object), (object), null, null, null, null, null, null, null, null, null, null, null, null, null]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Long
java.lang.Long object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80022c012 4 (alignment/padding gap) 16 8 long Long.value 1
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
String
java.lang.String object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80002da12 4 char[] String.value [S, t, r, i, n, g]16 4 int String.hash 020 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Byte
java.lang.Byte object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)8 4 (object header: class) 0xf80021eb12 1 byte Byte.value 113 3 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Boolean
java.lang.Boolean object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)8 4 (object header: class) 0xf800209712 1 boolean Boolean.value true13 3 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
其它
指针压缩
前置知识:32位操作系统为什么最多支持 4G 内存
- 先看一张8字节的内存:
如果需要寻址上面的所有格子:那么我们需要 2^6 次方个地址,即 6位操作系统。相同的算法我们计算32位的操作系统:
2^32 bit = 2^29 byte = 2^19 KB = 2^9 MB = 2^-1 GB = 0.5 GB实际值为0.5G,但是为什么说32位 CPU 最多支持 4G 内存呢?实际上CPU会把 8 bit(1Byte)当作一组,即最小的读取单元为 1 Byte, 因此 2^32 * 1 Byte = 4G// 实际上,能够使用的内存大小由两方面决定硬件和操作系统,操作系统指的是虚拟地址层面,而硬件指的是地址总线。
// 其它参考:https://www.zhihu.com/question/22594254/answer/42967413
从32位操作系统到64位操作系统
- 从上面我们知道32操作系统最多使用的内存为4G,随着我们开发的程序越来越复杂,32位操作系统已经不能满足我们的内存需求,我们进入了64操作系统的时代,我们可以使用的内存达到 4G * 2^32 ,但指针长度也达到了8个字节,过长的指针带来了新的问题:
1、增加了GC开销:64位对象引用需要占用更多的堆空间,留给其他数据的空间将会减少,从而加快了GC的发生,更频繁的进行GC。
2、降低缓存命中率:64位对象引用增大了,内存能缓存的oop将会更少,从而降低了缓存的效率。
指针压缩:使用4字节指针的同时获得更大的内存
如何开启指针压缩
-XX:+UseCompressedOops // 对象指针压缩
-XX:+UseCompressedClassPointers // 类元数据指针压缩// 如上示例中已开启
# Compressed references (oops): 3-bit shift
# Compressed class pointers: 3-bit shift// 64 JVM class point 占用4个字节
concurrency.A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00000053e25b7601 (hash: 0x53e25b76; age: 0)8 4 (object header: class) 0xf800c14312 4 int A.a 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
实现原理
// JVM 中 java对象默认8字节对齐 最大堆内存 32 GB(4G * 2^3),超过 32 GB 指针压缩将失效
-XX:ObjectAlignmentInBytes8字节对齐的情况下,地址的后三位总是为0:8 = 100016 = 1000024 = 1100032 = 10000040 = 10100048 = 11000056 = 11100064 = 100000072 = 1001000因此,在Java对象中存储时通过右移三位将3个0抹去,从内存中获取值时再通过将Java对象中的地址左移3位补0,从而实现使用4个字节获得 2^32 * 2^3 个内存地址,一个内存地址指向 1Byte 则总计32G内存(这也是为什么我们经常看到一些文章中说Java堆内存不要超过32G的原因,因为4字节指针,8字节对齐无法表示超过32内存,会关闭指针压缩,除非调整对齐字节数来扩大可访问的内存空间)。设置为16字节对齐:最大堆内存 64 GB(4G * 2^4),超过 64 GB 指针压缩将失效16 = 1000032 = 10000048 = 11000064 = 1000000
思考
mark word 数据字段为什么是不固定动态变化的
- 实现不增加对象的内存占用的情况下,支持对象锁并发和锁优化。
mark word 是字段动态变化的,当获取锁时 hash code 等字段被存储在哪
- HotSpot VM 若为偏向锁则未获取 hash code,若已获取 hash code 则不会获取偏向锁而是直接获取轻量级锁(若为偏向级锁,此时获取 hash code 则会膨胀为重量级锁),轻量级锁时 hash code 存放在 Lock Record 中,重量级锁时 hash code 存放在 ObjectMonitor 对象上。
- 注意:这里讨论的hash code都只针对identity hash code。用户自定义的hashCode()方法生成的 hash code 不会放在对象头。(Identity hash code是未被覆写的 java.lang.Object.hashCode() 或者 java.lang.System.identityHashCode(Object) 所返回的值。)
- 参考大R回答:https://www.zhihu.com/question/52116998/answer/133400077
个人简介
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。
🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。
💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。
🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
📖 保持关注我的博客,让我们共同追求技术卓越。
相关文章:

【Java对象】一文读懂 Java 对象庐山真面目及指针压缩
文章目录 版本及工具介绍Java 对象结构对象头mark word 标记字mark word 标记字解析Lock Record class point 类元数据指针 实例数据对齐填充为什么需要对齐填充 常见 Java 数据类型对象分析ArrayListLongStringByteBoolean 其它指针压缩前置知识:32位操作系统为什么…...
leetcode做题笔记210. 课程表 II
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。 例如,想要学习课程 0 ,你需要先完成课程 1…...

【深度学习 AIGC】stable diffusion webUI 使用过程,参数设置,教程,使用方法
文章目录 docker快速启动vae.ckpt或者.safetensorsCFG指数/CFG Scale面部修复/Restore facesRefinerTiled VAEClip Skipprompt提示词怎么写 docker快速启动 如果你想使用docker快速启动这个项目,你可以按下面这么操作(显卡支持CUDA11.8)。如…...

论文阅读 - Detecting Social Bot on the Fly using Contrastive Learning
目录 摘要: 引言 3 问题定义 4 CBD 4.1 框架概述 4.2 Model Learning 4.2.1 通过 GCL 进行模型预训练 4.2.2 通过一致性损失进行模型微调 4.3 在线检测 5 实验 5.1 实验设置 5.2 性能比较 5.5 少量检测研究 6 结论 https://dl.acm.org/doi/pdf/10.1145/358…...
PaddleMIX学习笔记(1)
写在前面 之前对HyperLedger的阅读没有完全结束,和很多朋友一样,同时也因为工作的需要,最近开始转向LLM方向。 国内在大模型方面生态做的最好的,目前还是百度的PaddlePaddle,所以自己也就先从PP开始看起了。 众所周知…...

【网络协议】聊聊HTTPS协议
前面的文章,我们描述了网络是怎样进行传输数据包的,但是网络是不安全的,对于这种流量门户网站其实还好,对于支付类场景其实容易将数据泄漏,所以安全的方式是通过加密,加密方式主要是对称加密和非对称加密。…...

2023.11.2事件纪念
然而造化又常常为庸人设计,以时间的流逝,来洗涤旧迹,仅以留下淡红的血色和微漠的悲哀。 回顾这次事件,最深的感触就是什么是团队的力量! 当我们看到希望快要成功的时候,大家洋溢出兴奋开心的表情,一起的欢声笑语;但看…...

Scala和Play WS库编写的爬虫程序
使用Scala和Play WS库编写的爬虫程序,该程序将爬取网页内容: import play.api.libs.ws._ import scala.concurrent.ExecutionContext.Implicits.global object BaiduCrawler {def main(args: Array[String]): Unit {val url ""val proxy…...

佳易王配件进出库开单打印进销存管理系统软件下载
用版配件进出库开单打印系统,可以有效的管理:供货商信息,客户信息,进货入库打印,销售出库打印,进货明细或汇总统计查询,销售出库明细或汇总统计查询,库存查询,客户往来账…...

【深度学习基础】专业术语汇总(欠拟合和过拟合、泛化能力与迁移学习、调参和超参数、训练集、测试集和验证集)
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...

【C语言:函数栈帧的创建与销毁】
文章目录 前言一、前期准备1.寄存器2.汇编指令3.测试代码 二、解开函数栈帧的神秘面纱1.栈帧大体轮廓2.main函数栈帧的创建3.main函数内执行有效代码4.烫烫烫5.函数参数的传递6.add函数栈帧的创建7.add函数内执行有效代码8.add是如何获得参数的9. add函数栈帧的销毁10.main函数…...
怎么在C++中实现云端存储变量
随着云计算技术的快速发展,现在我们可以将数据存储在云端,以便于在不同设备和地点访问。在C中,我们也可以通过一些方法来实现这个功能。本文将详细介绍如何在C中实现云端存储变量。 首先,我们需要理解,C本身并没有直接…...

短视频矩阵营销系统工具如何助力商家企业获客?
1.批量剪辑技术研发 做的数学建模算法,数学阶乘的组合乘组形式,采用两套查重机制,一套针对素材进行查重抽帧素材,一套针对成片进行抽帧素材打分制度查重,自动滤重计入打分。 2.账号矩阵分发开发 多平台,…...
PCL 计算一个平面与包围盒体素的相交线
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 基于之前计算的包围盒体素(PCL 包围盒体素化显示),这里使用一个平面与其进行相交,并求出与其中体素单元的相交线。 二、实现代码 //标准文件 #include <iostream> #include <thread>//PCL...
面向教育的计算机视觉和深度学习5
面向教育的计算机视觉和深度学习5 1. 好处智能内容(Smart Content)任务自动化(Task Automation)缩小技能差距(Closing Skill Gap) 2. 应用程序学生学习与福利(Student Learning and Welfare&…...
FPGA芯片内部结构
参考链接:FPGA的进阶之第二章FPGA芯片内部结构(2)...

人工智能AI创作系统ChatGPT网站系统源码+AI绘画系统支持GPT4.0/支持Midjourney局部重绘
一、前言 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建…...
Google 开源项目风格指南
目录 C 风格指南 Objective-C 风格指南 Python 风格指南 Shell 风格指南 TypeScript 风格指南 Javascript 风格指南 HTML/CSS 风格指南 C 风格指南 C 风格指南 - 内容目录 — Google 开源项目风格指南 Objective-C 风格指南 Objective-C 风格指南 - 内容目录 — Googl…...

无限上下文,多级内存管理!突破ChatGPT等大语言模型上下文限制
目前,ChatGPT、Llama 2、文心一言等主流大语言模型,因技术架构的问题上下文输入一直受到限制,即便是Claude 最多只支持10万token输入,这对于解读上百页报告、书籍、论文来说非常不方便。 为了解决这一难题,加州伯克利…...
学习剑指jvm
一直弱,jvm 1、主要解决运行状态的线上系统突然卡死,造成系统无法访问,甚至直接内存溢出异常(Out of Memory,OOM) 2、希望解决线上JVM垃圾回收的相关问题,但无从下手。 3、新项目上线,对设置…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...