【Java对象】一文读懂 Java 对象庐山真面目及指针压缩

news2024/12/23 9:22:08

文章目录

  • 版本及工具介绍
  • 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 类元数据指针。

企业微信截图_16873473617131.png

  • 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,   16

1407343478 // 对象 hashCode

concurrency.A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000053e25b7601 (hash: 0x53e25b76; age: 0)
  8   4        (object header: class)    0xf800c143
 12   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               VALUE
  0   8        (object header: mark)     0x00000096d75ff7e8 (thin lock: 0x00000096d75ff7e8)
  8   4        (object header: class)    0xf800c143
 12   4    int A.a                       2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

concurrency.A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000053e25b7601 (hash: 0x53e25b76; age: 0)
  8   4        (object header: class)    0xf800c143
 12   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个字节,具体字段如下所示:

企业微信截图_16873116017337.png

mark word 标记字解析
补充知识:
大端存储(Big-Endian):数据的高字节存储在低地址中,数据的低字节存储在高地址中
小端存储(Little-Endian):数据的高字节存储在高地址中,数据的低字节存储在低地址中

// 上文示例 Mark word 分析 JVM 64位
0x00000053e25b7601 (hash: 0x53e25b76; age: 0)

十六进制数: 0x00000053e25b7601
二进制数:   0000 0000 0000 0000 0000 0000 0101 0011
           1110 0010 0101 1011 0111 0110 0000 0001

锁标记: 01 无锁
分代年龄:0000 age:0
hashCode: 101 0011 1110 0010 0101 1011 0111 0110 = hash: 0x53e25b76 = 十进制:1407343478

0x00000096d75ff7e8 (thin lock: 0x00000096d75ff7e8)

十六进制数: 0x00000096d75ff7e8
二进制数:   0000 0000 0000 0000 0000 0000 1001 0110
           1101 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字节。

企业微信截图_16904489558170.png

  • 指针压缩技术也依赖 Java 对象字节对齐。

常见 Java 数据类型对象分析

ArrayList

java.util.ArrayList object internals:
OFF  SZ                 TYPE DESCRIPTION               VALUE
  0   8                      (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                      (object header: class)    0xf8002f39
 12   4                  int AbstractList.modCount     3
 16   4                  int ArrayList.size            3
 20   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               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80022c0
 12   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               VALUE
  0   8          (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4          (object header: class)    0xf80002da
 12   4   char[] String.value              [S, t, r, i, n, g]
 16   4      int String.hash               0
 20   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               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80021eb
 12   1   byte Byte.value                1
 13   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               VALUE
  0   8           (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4           (object header: class)    0xf8002097
 12   1   boolean Boolean.value             true
 13   3           (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

其它

指针压缩

前置知识:32位操作系统为什么最多支持 4G 内存

  • 先看一张8字节的内存:
    企业微信截图_16873372863874.png
如果需要寻址上面的所有格子:那么我们需要 2^6 次方个地址,即 6位操作系统。

相同的算法我们计算32位的操作系统:
2^32 bit = 2^29 byte = 2^19 KB = 2^9 MB = 2^-1 GB = 0.5 GB

实际值为0.5G,但是为什么说32CPU 最多支持 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               VALUE
  0   8        (object header: mark)     0x00000053e25b7601 (hash: 0x53e25b76; age: 0)
  8   4        (object header: class)    0xf800c143
 12   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:ObjectAlignmentInBytes

8字节对齐的情况下,地址的后三位总是为08 =    1000
 16 =   10000
 24 =   11000
 32 =  100000
 40 =  101000
 48 =  110000
 56 =  111000
 64 = 1000000
 72 = 1001000
 
 因此,在Java对象中存储时通过右移三位将30抹去,从内存中获取值时再通过将Java对象中的地址左移3位补0,从而实现使用4个字节获得 2^32 * 2^3 个内存地址,一个内存地址指向 1Byte 则总计32G内存
 (这也是为什么我们经常看到一些文章中说Java堆内存不要超过32G的原因,因为4字节指针,8字节对齐无法表示超过32内存,会关闭指针压缩,除非调整对齐字节数来扩大可访问的内存空间)。
 
 设置为16字节对齐:最大堆内存 64 GB(4G * 2^4),超过 64 GB 指针压缩将失效
 16 =   10000
 32 =  100000
 48 =  110000
 64 = 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编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

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

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

相关文章

AI写作软件哪个好?这3个AI写作神器用了都说好!

随着信息时代的快速发展&#xff0c;AI写作早已成为人们创作内容的重要途径之一&#xff0c;在使用AI软件进行创作之前&#xff0c;当然要选择一个优质的写作软件&#xff0c;不过只要你拥有了这3款写作神器&#xff0c;你就能轻松创作出高质量的文章&#xff0c;我们一起来看看…

【uniapp】uview1.x 的 u-upload 上传点击删除隐藏 modal 提示框

uview1.x 版本的 upload 默认在图片成功上传后&#xff0c;再点击右上角删除按钮时会弹出提示框&#xff0c;如图&#xff1a; 但是有时又不需要&#xff0c;想要直接提示删除成功即可&#xff0c;由于官网没有给出点击删除按钮时所调用的钩子函数&#xff0c;又无法操作 DOM&…

【unity小技巧】实现由滑动条控制音量的大小

文章目录 前言开始1.配置BGM2.滑动条3.文本组件4.新增音量控制脚本 完结 前言 这期来一个比较基础的课程&#xff0c;也是比较常用的&#xff0c;unity使用滑动条控制音量的大小 开始 1.配置BGM 2.滑动条 3.文本组件 4.新增音量控制脚本 public class VolumeController : M…

数据库实验:SQL的数据视图

目录 视图概述视图的概念视图的作用 实验目的实验内容实验要求实验过程 视图概述 视图是由数据库中的一个表或多个表导出的虚拟表&#xff0c;其作用是方便用户对数据的操作 视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一…

关于ROS的网络通讯方式TCP/UDP

一、TCP与UDP TCP/IP协议族为传输层指明了两个协议&#xff1a;TCP和UDP&#xff0c;它们都是作为应同程序和网络操作的中介物。 **TCP&#xff08;Transmission Control Protocol&#xff09;协议全称是传输控制协议&#xff0c;是一种面向连接的、可靠的、基于字节流的传输…

树莓派4无法进入桌面模式(启动后出现彩色画面,然后一直黑屏,但是可以正常启动和ssh)

本文记录了这段比较坎坷的探索之路&#xff0c;由于你的问题不一定是我最终解决方案的&#xff0c;可能是前面探索路上试过的&#xff0c;所以建议按顺序看排除前置问题。 双十一又买了个树莓派 4B&#xff0c;插上之前树莓派 4B 的 TF 卡直接就能使用&#xff08;毕竟是一样规…

Java 8 新特性 Stream 的使用场景(不定期更新)

方便在写代码的过程中直接使用&#xff0c;好记性不如好文章&#xff0c;直接 CV 改了直接用。提高 办&#xff08;摸&#xff09;公&#xff08;鱼&#xff09;效&#xff08;时&#xff09;率&#xff08;间&#xff09;&#xff0c; 不然就直接问 GPT 也不是说不行。 只符合…

win10 + cmake3.17 编译libpng-1.6.34

需要预先编译zlib库当前的根目录为&#xff1a;D:\Depend_3rd_party\libpngx64\ 1. 下载并解压libpng-1.6.34&#xff0c;得到 D:\Depend_3rd_party\libpngx64\libpng-1.6.34 2. 创建build文件夹&#xff0c;install文件夹&#xff0c;得到 D:\Depend_3rd_party\libpngx64\i…

数据库--数据库约束/聚合查询/分组查询/联合查询

前言 逆水行舟&#xff0c;不进则退&#xff01;&#xff01;&#xff01; 目录 数据库约束 聚合查询 分组查询 联合查询 联合查询---内连接与外连接 补充 联合查询用到的代码 数据库约束 not null 约束&#xff1a;在创建表的时候&#xff0c;可以指定列…

【实验记录】为了混毕业·读读论文叭

PR曲线 1. Robust_Place_Recognition_using_an_Imaging_Lidar 在第三节方法中&#xff0c;提到了一些列处理步骤&#xff0c;分析来与vins相似&#xff0c;在vins中是关键帧检索、特征提取、DBoW查询、描述子匹配、PnP RANSAC求解。 第四节的实验部分&#xff0c;没有绘制pr…

MyBatis 详解

目录 1.MyBatis 框架的搭建 1.1 创建数据库和表 1.2 添加 MyBatis 依赖 1.3 设置 MyBatis 配置 1.3.1 设置数据库的连接信息 1.3.2 设置 XML 保存路径和命名格式 1.4 根据 MyBatis 写法完成数据库得操作 1.4.1 定义接口 1.4.2 使用 XML 实现接口 2.MyBatis查询操作 …

手记系列之七 ----- 分享Linux使用经验

前言 本篇文章主要介绍的关于本人在使用Linux记录笔记的一些使用方法和经验&#xff0c;温馨提示&#xff0c;本文有点长&#xff0c;约1.7w字&#xff0c;几十张图片&#xff0c;建议收藏查看。 一、Linux基础使用 1&#xff0c;服务器查看及时日志 tail -500f catalina.out …

npm的使用

package.json 快速生成package.json npm init -y “version”: “~1.1.0” 格式为&#xff1a;「主版本号. 次版本号. 修订号」。 修改主版本号是做了大的功能性的改动 修改次版本号是新增了新功能 修改修订号就是修复了一些bug dependencies "dependencies": {&…

计算机组成与结构-计算机体系结构

计算机体系结构 指令系统 Flynn分类法 SISD&#xff08;单指令流单数据流&#xff09; 结构 控制部分&#xff1a;一个处理器&#xff1a;一个主存模块&#xff1a;一个 代表 单处理器系统 SIMD&#xff08;单指令流多数据流&#xff09; 结构 控制部分&#xff1a;一个处理…

2022年09月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 十六进制数100&#xff0c;对应的十进制数为 &#xff1f;&#xff08; &#xff09; A: 128 B: 256 C: 28 D: 56 答…

基于Qt命令行处理XML文件读写

Qt源码在后面,文本介绍Qt国际化语言和XML # XML基础(一) ## 1、概述 ### 1.1 定义(xml是个啥玩意儿?) XML(extensible Markup Language)俗称差妹儿,专业称之为:可拓展标记语言。 (1)何为标记,指的是一种标记语言,或者标签语言,即用一系列的标签来对数据进行…

Leetcode—421.数组中两个数的最大异或值【中等】明天写一下字典树做法!!!

2023每日刷题&#xff08;十九&#xff09; Leetcode—421.数组中两个数的最大异或值 算法思想 参考自灵茶山艾府 实现代码 class Solution { public:int findMaximumXOR(vector<int>& nums) {int maxValue *max_element(nums.begin(), nums.end());int highId…

STM32笔记-AD模数转换

目录 一、ADC介绍 二、ADC主要特征 三、ADC框图 1. ​​​​ 外部触发转换 ​ 2. 转换模式 3. 输入通道 4. 逻辑框图 四、校准 五、数据对齐 六、AD转换步骤 七、AD_Init(单通道AD转换)初始化函数配置 一、ADC介绍 1. 12位ADC是一种逐次逼近型模拟数字转换器。它有多达…

扣人心弦玄幻小说,故事爽度堪比热门IP,堪称史诗级作品

《万古不死&#xff0c;葬天&#xff0c;葬地&#xff0c;葬众生》 这本书是我看过最符合我心意的长生文。它始终在刻画一个长生者的经历&#xff0c;并且每一次的离别和悲伤都恰到好处。主角不是直接睡到无敌然后出去浪&#xff0c;而是不断学习&#xff0c;智商在线。坑杀雷族…

垃圾回收系统小程序定制开发搭建攻略

在这个数字化快速发展的时代&#xff0c;垃圾回收系统的推广对于环境保护和可持续发展具有重要意义。为了更好地服务于垃圾回收行业&#xff0c;本文将分享如何使用第三方制作平台乔拓云网&#xff0c;定制开发搭建垃圾回收系统小程序。 首先&#xff0c;使用乔拓云网账号登录平…