Java对象的内存布局全流程

 更新时间:2022年05月05日 15:32:27   作者:旧时明月丶  
这篇文章主要介绍了Java对象的内存布局全流程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

开始先抛出一个问题:一个对象o,Object o = new Object();创建完成后会占用多少字节的内存?

要能回答这个问题,就需要了解java对象的内存布局。

对象内存布局

一个Java对象在内存中包括对象头、实例数据和对齐填充三个部分。如下图所示:

在这里插入图片描述

对象头

  • Mark Word:包含一系列的标记位比如hashcode、GC分代年龄、偏向锁位,锁标志位等。这个Mark Word在对象被加了不同量级的锁时所包含的内容和布局都有所不同,这涉及到锁升级的知识,暂不展开讨论
  • Klass Pointer:是一个指针,指向描述这个对象类型的元对象,例如Object.class,User.class等

实例数据

  • instance data:描述成员变量的信息,如果成员变量是引用类型,那么它就是一个指针。instance data的大小是所有成员变量的占用空间(基本数据类型大小+指针大小)

对齐

  • padding:在java中,为了能够更加高效的利用内存空间,会将对象大小设定为8bytes的整数倍,如果对象头+实例数据的大小不是8bytes的倍数,那么会在padding区域填充几个字节,使得对象占用空间是8bytes的倍数

那么对象布局中各个部分占用内存空间到底多大呢?

对象占用内存空间

由于目前64位操作系统已经基本普及,下面只分析64位操作系统下的情况

指针压缩

在64位系统中,一个指针占64 bits也就是8 bytes,而在32位系统中指针只占4个字节,于是为了能够减少内存消耗,从JDK1.6开始,JVM会默认支持指针压缩,会将指针大小压缩成4个字节,

这涉及到两个参数-XX:+UseCompressedOops,-XX:+UseCompressedClassPointers。

  • UseCompressedOops:oops: ordinary object pointer,普通对象指针压缩,例如Object o = new Object();其中o就是个指向new Object()对象的指针,o在指针压缩前占用8个字节,在指针压缩后占用4个字节
  • UseCompressedClassPointers:压缩Klass Pointer,压缩前8个字节,压缩后4个字节

对象头

Mark Word占8个字节

  • Klass Pointer:开启(默认)压缩4个字节,不开启压缩8个字节

实例数据

  • instance data:根据实际情况计算:如果成员变量是基本数据类型,那么占用空间就是基本数据类型的大小,Java的8大基本数据类型的大小如下:
数据类型占用空间bytes
byte1
short2
int4
long8
float4
double8
char2
boolean1

如果成员变量是引用类型,那么就是一个指针大小(开启指针压缩占4字节,不开启指针压缩8字节)

对齐

  • padding:如果对象头+实例数据的大小不是8 bytes的倍数,那么就填充这个区域,使得对象占用空间能被8个字节整除(最小情况)

口说无凭,下面将会通过实验证明

证明对象内存布局

我们需要引用一个依赖:openjdk提供的jol-core:

<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.9</version>
</dependency>

1.查看默认情况下没有成员变量的对象布局

示例代码:

public class TestObj {
    public static void main(String[] args) {
        // 创建对象
        Object o = new Object();
        // 获得对象布局内容
        String s = ClassLayout.parseInstance(o).toPrintable();
        // 打印对象布局
        System.out.println(s);
    }
}

输出结果:

在这里插入图片描述

其中对象头(object header)有三个,前两个是Mark Word一共8个字节,后面一个是Klass Pointer,占4个字节,由于没有成员变量,所以实例数据没有占用空间,而最后4个字节描述信息为:loss due to the next object alignment,意思就是为了与下一个对象对齐而丢失的部分,也就是对齐填充空间

2.证明Klass Pointer在不开启压缩的情况下占用8个字节

我们只需要在jvm参数上加上-XX:-UseCompressedClassPointers即可,在IDEA工具中可以设置启动参数:

在这里插入图片描述

还是运行上述代码,运行程序结果:

在这里插入图片描述

如上图所示,对象头已经占用16个字节,前8个字节是Mark Word,后8个字节就是未压缩的Klass Pointer。我们还注意到对齐填充也没有了,原因是此时对象占用空间16个字节已经是8bytes的倍数,所以不需要填充,这完全印证了前面的分析

3.证明实例数据的存在以及大小

示例代码:

public class TestObj {
    public static void main(String[] args) {
        // 创建对象
        User user = new User(1, "zhangsan");
        // 获得对象布局内容
        String s = ClassLayout.parseInstance(user).toPrintable();
        // 打印对象布局
        System.out.println(s);
    }
}
class User {
    private int id;
    private String name;
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

打印结果:

在这里插入图片描述

如上图所示,int类型的id占用4个字节,指向字符串对象的name指针占用4个字节,加上对齐,对象一共占用24 bytes

4.最后验证不开启指针压缩的情况下指针占用8 bytes

只需在jvm参数上加上-XX:-UseCompressedOops:

在这里插入图片描述

还是运行上面的代码,打印结果:

在这里插入图片描述

很显然,此时name指针已经占用了8个字节

一般来说,UseCompressedClassPointers和UseCompressedOops是默认开启的,我们无需关心也无需修改。但是有个隐藏的细节就是:UseCompressedClassPointers的开启依赖UseCompressedOops的开启,并且开启UseCompressedOops 也默认强制开启UseCompressedClassPointers,关闭UseCompressedOops 默认关闭UseCompressedClassPointers。

至此,关于java对象的内存布局已经有了一个基本的了解,那么文章一开始的问题现在应该能很轻松的回答:16个字节。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Redis command timed out两种异常情况的解决方式

    Redis command timed out两种异常情况的解决方式

    Redis是我们开发中常用的数据库,下面这篇文章主要给大家介绍了关于Redis command timed out两种异常情况的解决方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • java数据结构之插入排序

    java数据结构之插入排序

    这篇文章主要为大家详细介绍了java数据结构之插入排序的相关代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 关于aop切面 注解、参数如何获取

    关于aop切面 注解、参数如何获取

    这篇文章主要介绍了关于aop切面 注解、参数如何获取,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。
    2022-01-01
  • Java文件操作类 File实现代码

    Java文件操作类 File实现代码

    这篇文章主要介绍了Java文件操作类 File实现代码,需要的朋友可以参考下
    2017-08-08
  • MyBatis接口绑定的实现方式和工作原理

    MyBatis接口绑定的实现方式和工作原理

    在日常开发中,数据持久层是几乎每个项目都会涉及的一个关键组成部分,MyBatis作为一个流行的持久层框架,其提供的接口绑定机制极大地简化了数据库操作,本文将通过详细的代码示例和讲解,带你深入理解MyBatis接口绑定的工作原理和实践方式,需要的朋友可以参考下
    2024-03-03
  • Sharding-Proxy基本功能用法介绍

    Sharding-Proxy基本功能用法介绍

    这篇文章介绍了Sharding-Proxy基本功能用法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-02-02
  • SpringBoot中yml多环境配置的3种方法

    SpringBoot中yml多环境配置的3种方法

    这篇文章主要给大家介绍了SpringBoot中yml多环境配置的3种方法,文中有详细的代码示例供大家参考,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-10-10
  • Apache DolphinScheduler实现自动化打包单机/集群部署详解

    Apache DolphinScheduler实现自动化打包单机/集群部署详解

    这篇文章主要为大家介绍了Apache DolphinScheduler实现自动化打包单机/集群部署详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 深入理解Java8新特性之Stream API的终止操作步骤

    深入理解Java8新特性之Stream API的终止操作步骤

    Stream是Java8的一大亮点,是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation)或者大批量数据操作。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性,感兴趣的朋友快来看看吧
    2021-11-11
  • SpringAop中AspectJ框架的切入点表达式

    SpringAop中AspectJ框架的切入点表达式

    这篇文章主要介绍了SpringAop中AspectJ框架的切入点表达式,AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持,@AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面,需要的朋友可以参考下
    2023-08-08

最新评论