Java对象的内存结构

news2024/9/19 10:34:46

文章目录

  • 概述
    • 1. 对象头 (Header)
      • Mark Word
        • 1. 32位HotSpot虚拟机中的MarkWord
        • 2. 64位HotSpot虚拟机中的MarkWord
      • Class Pointer
      • Array Length
      • 指针压缩原理
      • 指针压缩测试
    • 2. 实例数据 (Instance Data)
    • 3. 填充数据 (Padding Data)
  • 查看 Java 对象的内存结构
    • 使用反射和VisualVM、JConsole等工具
    • 使用JOL
      • 引入Maven依赖
      • 测试样例
  • 参考

概述

Java 对象的内存结构对于理解 Java 内存管理和性能优化至关重要。本文将详细介绍 Java 对象的内部结构,并提供查看这些信息的方法。
Java 对象由三个主要部分组成:

  • 对象头 (Header)
  • 实例数据 (Instance Data)
  • 填充数据 (Padding Data)
    在这里插入图片描述

1. 对象头 (Header)

对象头主要用于存储对象自身的运行时数据,如哈希码、分代年龄、锁状态等。
一般对象头分为三部分:

  • Mark Word:存储对象的哈希值、分代年龄、锁状态等。
  • Class Pointer:存储对象的类的元数据的指针。
  • Array Length:用于存储数组的长度,占4字节。

Mark Word

Mark Word 是对象头中最关键的部分,它包含了对象的哈希值、分代年龄、锁状态等。Mark Word 的具体内容会根据锁的状态改变而改变。

众所周知,计算机分为32位和64位,操作系统也分为32位和64位。32位计算机需要安装32位的操作系统,64位计算机需要安装64位系统。32位操作系统最大内存是4GB,因为32位二进制能够表示的最大数是2^32,即22102410241024。而64位操作系统能够访问的最大内存是2^64,即16EB1

1. 32位HotSpot虚拟机中的MarkWord

在这里插入图片描述

2. 64位HotSpot虚拟机中的MarkWord

在这里插入图片描述
考虑到现在基本都是在64位操作系统上安装了64位的JDK,JVM自然也是64位的。本文以64位HotSpot为例,介绍Mark Word结构,其他JVM中的Java对象内存结构可能有所差异,大家可以自行查阅相关资料。

Mark Word中各字段是什么含义呢?

  • hashCode:对象本身的哈希码。调用方法 System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord中没有足够的空间保存它,因此会被移动到线程 Monitor中。
  • 分代年龄:在GC中,当survivor区中对象复制一次,年龄加1。如果到15之后会移动到老年代,并发GC的年龄阈值为6。
  • 是否偏向锁:对象是否存在偏向锁标记
  • 锁标志位:对象的锁标志状态。在并发的情况下,可以通过锁标志判断象是否被线程占用。01是初始状态,未加锁。随着锁级别的不同,对象头里会存储不同的内容。
    • 偏向锁存储的是当前占用此对象的线程ID(此处的线程ID是操作系统层面的线程唯一ID,与Java中的线程ID是不一致的,了解即可)。
    • 轻量级锁存储的是指向线程栈中锁记录的指针
    • 重量级锁存储的是指向互斥锁的指针
  • Epoch:偏向锁的时间戳
(是否偏向锁)锁标志位 2bit锁状态
0(代表无锁)01无锁态(new)
1(偏向锁)01偏向锁
-00轻量级锁(自旋锁、无锁、自适应自旋锁)
-10重量级锁
-11GC 标记

TODO:以上各种锁的应用及升级情形也是一个大工程,容我以后再补。^_^

可以看到64位虚拟机其实是浪费了一部分空间的,JVM支持通过-XX:+UseCompressedOops -XX:-UseCompressedClassPointers参数来进行指针压缩。

Class Pointer

Class Pointer,也叫Class Metadata Address,这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过它确定对象是哪个类的实例。该指针的位长度为JVM的一个Word大小,即 32位的JVM 为 32位,64位的JVM为 64位。
如果应用的对象过多,使用 64位的指针将浪费大量内存,统计而言,64位的 JVM将会比 32位的 JVM多耗费 50%的内存。为了节约内存可以使用选项 +UseCompressedOops开启指针压缩,其中,oop即 ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

  1. 每个 Class的属性指针(即静态变量)
  2. 每个对象的属性指针(即对象变量)
  3. 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针 JVM不会优化,比如指向 PermGen的 Class对象指针(JDK8中指向元空间的 Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
在64位JVM虚拟机中Mark Word、Class Pointer这两部分都是64位的,所以也就是需要128位大小(16 bytes)。从JDK1.6 update14开始,在64位操作系统中,JVM开始支持指针压缩。JDK1.8默认开启了指针压缩。

参数作用
-XX:+UseCompressedOops开启对象指针压缩
-XX:-UseCompressedOops关闭对象指针压缩
-XX:+UseCompressedClassPointers开启Class Pointer指针压缩
-XX:-UseCompressedClassPointers关闭Class Pointer指针压缩

一般只有虚拟机内存达到32G以上,4个字节已经无法满足寻址需求时,才需要关闭指针压缩

Array Length

用于存储数组的长度,这是数组类型的对象独有的,占用4字节。

指针压缩原理

在JVM里,对象都是以8字节对齐的,即对象的大小都是8的倍数,所以不管在32位还是64位的JVM里对象地址的末尾3位始终都是0。
例如:

  • 08 = 00001000
  • 16 = 00010000
  • 24 = 00011000
  • 32 = 00100000

既然JVM已经知道了这些对象的内存地址后三位始终是0,那么这些0就没必要在内存中继续存储。相反,我们可以利用这3位存储一些有意义的信息,这样我们就多出3位的寻址空间,也就是说如果我们继续使用32位来存储指针,只不过后三位被我们用来存放有意义的地址空间信息,当寻址的时候,JVM将这32位的对象引用左移3位(后三位补0)即可。我们原本32位的内存寻址空间一下变成了35位,可寻址的内存空间变为2 ^ 35,2 ^ 5 * 1024 * 1024 * 1024 = 32G,也就是说在32位系统JVM的内存可扩大到32G了,基本上可满足大部分应用的使用了。
现在电脑内存普遍在8GB、16GB,而16GB内存的地址空间范围是0000000000000000000000000000000000 - 1111111111111111111111111111111111。
假如创建对象时申请到一个内存地址,位置是8589934616(2 ^ 33 + 3 * 8),下面以这个地址演示指针压缩的原理。
指针压缩有两个过程,分别是编码和解码。

  • 编码:编码是指获得到内存地址后,右移三位,得到压缩后的内存地址。
    8589934616转换为二进制为1000000000000000000000000000011000,右移三位得到1000000000000000000000000000011,即1073741827,它就是压缩后的内存地址。
  • 解码:解码是在操作内存前,把原来地址左移三位,得到真实的内存地址。
    1073741827转换为二进制为1000000000000000000000000000011,左移三位得到1000000000000000000000000000011000,即8589934616,它就是真实的内存地址。
    在这里插入图片描述
    32位系统的最大内存地址是2 ^ 32,即4294967296。它小于上述演示地址8589934616,所以32位系统不能直接操作它,但借助一些辅助技术就可以直接进行操作了。
    其实上面涉及到JVM的一个参数:ObjectAlignmentInBytes,这个参数表示Java堆中的对象,需要按照几字节对齐,范围是[8-256],必须是2的n次方。在JDK1.8中该参数默认值是8,也就是Java默认是8字节对齐。此时,如果配置最大堆内存超过32GB(实际略小于32GB就会失效),那么指针压缩会失效。
    你可以使用-XX:ObjectAlignmentInBytes=16修改该配置为16,那么最大堆内存超过64GB时指针压缩才会失效。同理,配置为32时,超过128GB才会失效。

虽然增大ObjectAlignmentInBytes能扩大寻址范围,这同时也会增加对象之间的填充数据,浪费内存和缓存空间,从而导致压缩指针没有达到原本节省空间的效果。

-XX:ObjectAlignmentInBytes=16

# 查看ObjectAlignmentInBytes配置
java -XX:+PrintFlagsFinal -version | grep ObjectAlignmentInBytes
     intx ObjectAlignmentInBytes                    = 8                                   {lp64_product}
java version "1.8.0_421"
Java(TM) SE Runtime Environment (build 1.8.0_421-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.421-b09, mixed mode)

指针压缩测试

写一个简单的User类,使用JOL(不熟悉的请参考后面的教程)对它进行测试,代码如下:

package org.hbin.jol;

import org.openjdk.jol.info.ClassLayout;

import java.util.Date;

/**
 * @author Haley
 * @version 1.0
 * 2024/8/22
 */
public class JOLTest {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
    }

    static class User {
        long id;
        String name;
        int age;
        Date birthday;

        Date createTime;

        int[] arr;
    }
}

测试结果:

UseCompressedOopsUseCompressedClassPointers对象大小
++40 bytes
--64 bytes
+-48 bytes
-+64 bytes1

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 实例数据 (Instance Data)

实例数据部分存储了对象的实际数据,也就是对象属性的值。这部分的大小取决于对象的属性数量和类型。

3. 填充数据 (Padding Data)

为了方便虚拟机的寻址,JVM要求对象结构的内存大小是8字节的整数倍。如果对象的大小的字节数不是8的倍数,那么 JVM 会添加一些额外的字节达到8的倍数来满足对齐要求。
例如对象头12个字节,实例数据只有一个byte变量,则填充数据是3个字节,12+1+3=16,是8的倍数。

填充数据不是必须存在的,仅仅是为了字节对齐。
只有存在填充数据时才有,否则为0字节

查看 Java 对象的内存结构

使用反射和VisualVM、JConsole等工具

使用反射可以来获取对象的内部信息,如属性信息、方法信息等。
利用VisualVM、JConsole等工具也可以查看对象在JVM中占用的空间信息、回收信息等。

使用JOL

Java对象布局(Java Object Layout),也叫JOL,是一个用来分析 JVM 中对象内存布局的小工具。可以用于查看对象在内存中的占用情况,实例对象的引用情况等。

引入Maven依赖

当前最新版本为0.17,你也可以根据需要自行选择或查找最新版本。

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

测试样例

package org.hbin.jol;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author Haley
 * @version 1.0
 * 2024/8/22
 */
public class JOLTest {
    public static void main(String[] args) {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseInstance(new int[]{1, 2, 3}).toPrintable());
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
    }
}

输出信息:

# 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

[I object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf800016d
 12   4        (array length)            3
 16  12    int [I.<elements>             N/A
 28   4        (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以切换指针压缩的开关,查看各对象在内存中占用的空间信息是否有变化。

参考

  • JVM–对象的结构
  • JAVA基础篇–JVM–3对象结构
  • JOL(java object layout): java 对象内存布局
  • 常见面试-JVM-指针压缩

  1. 关闭UseCompressedOops而打开UseCompressedClassPointers的情况下会输出一个警告:Java HotSpot(TM) 64-Bit Server VM warning: UseCompressedClassPointers requires UseCompressedOops,这是老版本遗留的BUG。从 Java 15 Build 23 开始, UseCompressedClassPointers 已经不再依赖 UseCompressedOops 了,两者在大部分情况下已经独立开来。 ↩︎ ↩︎

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

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

相关文章

linux下的oracle启动命令

一、服务器断电后&#xff0c;手工启动oracle数据库步骤如下&#xff1a; 1、进入数据库服务器&#xff0c;切换到oracle用户,命令&#xff1a;su - oracle 2、启动数据库&#xff0c;命令&#xff1a; 1&#xff09; sqlplus / as sysdba 2) startup 3&#xff09;如果数据库已…

Rabbit mq 虚拟机stop无法重启

之前从后台进去&#xff0c;这个地方死活无法重启 然后重启docker 以及mq都不行 docker exec -it <CONTAINER_ID_OR_NAME> /bin/bash rabbitmqctl stop_app rabbitmqctl start_app 最后删除虚拟机&#xff0c;然后重建就行了 rabbitmqctl delete_vhost / rabbitmqctl…

C++ | Leetcode C++题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution { public:bool canMeasureWater(int x, int y, int z) {if (x y < z) {return false;}if (x 0 || y 0) {return z 0 || x y z;}return z % gcd(x, y) 0;} };

iPhone设备使用技巧:忘记密码的情况下如何解除iOS 18/17屏幕时间

我们给了儿子一部新手机。在尝试擦除旧手机上的所有内容并恢复出厂设置时&#xff0c;它要求提供 4 位屏幕时间密码。我已经尝试了我们会使用的所有可能性&#xff0c;但无法弄清楚。我们如何绕过这个问题或将手机恢复出厂设置以便我们可以出售它&#xff1f; Apple 社区 对于…

小琳AI课堂:Langchain

大家好&#xff0c;这里是小琳AI课堂&#xff0c;今天我们要探索一个令人兴奋的AI新概念——Langchain。 想象一下&#xff0c;如果我们可以把强大的大型语言模型&#xff0c;比如GPT-3&#xff0c;像乐高积木一样组合起来&#xff0c;会怎么样&#xff1f;这就是Langchain的核…

自定义开屏启动广告页

自定义开屏启动广告页 文章目录 自定义开屏启动广告页效果图简单版轮播方式css 效果图 简单版 图片 倒计时 <template><view class"guide fcc" :style"{ background: url(${ imgUrl }) no-repeat}"><view class"skip_btn" cli…

矢泽妮可二次元html视频动态引导页源码

源码介绍 矢泽妮可二次元html视频动态引导页源码 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码下载 矢泽妮可二次…

Linux系统下的容器安全:深入解析与最佳实践

在云计算和微服务架构的推动下&#xff0c;容器技术因其高效、可移植和灵活的特点&#xff0c;已经成为现代软件开发和部署的首选方案。然而&#xff0c;容器的广泛应用也带来了新的安全挑战&#xff0c;尤其是在Linux系统下&#xff0c;容器安全的实现和维护变得尤为重要。本文…

了解芯片的四大主流架构

四大主流芯片架构&#xff0c;犹如科技领域的四大支柱&#xff0c;各自矗立于技术创新的巅峰。这四大架构——X86、ARM、RISC-V与MIPS&#xff0c;不仅是芯片设计的基石&#xff0c;更是推动信息技术进步的强大动力。 一、芯片架构是什么&#xff1f; 芯片架构是指对芯片的类…

C++ 设计模式——外观模式

外观模式 C 设计模式——外观模式主要组成部分1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;Subsystem&#xff09;3. 客户端&#xff08;Client&#xff09; 例一&#xff1a;工作流程示例1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;…

GPU池化技术在油气勘探开发中的应用

01 背景介绍 国内某研究院为实现石油勘探开发专业软件资源的统一管理、统一监控、统一共享和统一计量&#xff0c;自主研发了勘探云管理平台(EPCP)和科研工作业务协同平台。该研究院通过两个平台实现了数十种专业勘探开发软件的共享&#xff0c;种类包括地震资料处理和解释&am…

中国四向穿梭车各角色、各玩家:大盘点

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 四向穿梭车作为现代物流自动化的关键设备&#xff0c;正在全球范围内迅速发展。 本文将对四向穿梭车的不同类别的厂商进行大盘点&#x…

pdfplumber - pdf 数据提取

文章目录 一、关于 pdfplumber安装 二、命令行界面1、基本示例2、选项 三、Python库1、基本示例2、加载PDF3、pdfplumber.PDF类4、pdfplumber.Page 类5、对象char特性line属性rect属性curve 属性派生属性image属性 6、通过pdfminer获取更高级别的pdfminer.six 四、可视化调试1、…

92.WEB渗透测试-信息收集-Google语法(6)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;91.WEB渗透测试-信息收集-Google语法&#xff08;5&#xff09; 监控的漏洞也有很多 打…

探索人工智能的未来:埃里克·施密特2024斯坦福大学分享四

一、语言模型的经济影响 关于语言模型的经济影响&#xff0c;我想先谈谈市场的影响。我们看到一些服务领域的变化速度比预期的要慢&#xff0c;比如 CHEG 和其他相关服务的表现。对此&#xff0c;您是否认为学术界应该获得人工智能补贴&#xff1f;还是说&#xff0c;他们应该…

树刷题codetop!!暴打面试题!!!!

题源codetop标签近半年树 1.二叉树的层序遍历2.二叉树的层序遍历II3.二叉树的锯齿形层次遍历4.N叉树的层次遍历5.二叉树的最近公共祖先6.二叉搜索树的最近公共祖先7.二叉树的直径8.二叉树中最大路径和9.二叉树的前序遍历10.从前序与中序遍历序列构造二叉树11.从中序与后序遍历序…

Tomcat:企业 WEB 奇境的开启密钥

目录 一.Tomcat 优势 二.前端三大核心技术 1.HTML 2.CSS&#xff08;Cascading Style Sheets&#xff09;层叠样式表 3.JavaScript 三.WEB框架 1.web资源和访问:PC 端或移动端浏览器访问 2.web资源和访问:手机 App 访问 四.单体架构 五.微服务 1.微服务的优点 2.微…

【第55课】XSS防御HttpOnlyCSP靶场工具等

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0…

Spring MVC图解

浏览器 (客户端请求) 用户通过浏览器发起HTTP请求&#xff0c;这个请求首先被Spring MVC的DispatcherServlet捕获。DispatcherServlet是前端控制器&#xff0c;用于协调整个请求处理过程。DispatcherServlet (前的端控制器) DispatcherServlet接收到请求后&#xff0c;首先会根…

推荐一款低成本 小尺寸数字脉冲编码调制(PCM)输入D类功率放大器 MAX98357AETE+T 兼具AB类性能

MAX98357AETET是数字脉冲编码调制(PCM)输入D类功率放大器&#xff0c;可提供AB类音频性能&#xff0c;同时具有D类的效率。器件在I2S/左对齐模式下通过单个增益设置输入可提供5中可选择增益(3dB、6dB、9dB、12dB、15dB)&#xff0c;在TDM模式下为固定12dB增益。 数字音频接口高…