一、先上答案

这个问题有坑,有两种回答

第一种解释:

object实例对象,占16个字节。

第二种解释:

Object o:普通对象指针(ordinary object pointer),占4个字节。

new Object():object实例对象,占16个字节。

所以一共占:4+16=20个字节。

第二种解释就是在玩文字游戏了,但还是要知道的。

二、这个答案适用于所有情况吗

并不是,这个答案只适用于现在一般默认情况。

准确的说,只适用于Hotspot实现64位虚拟机,默认开启了压缩类指针压缩普通对象指针的情况下。

本文下述内容若无特殊说明,指的都是JDK8 Hotspot实现64位虚拟机的未开启压缩的情况。

三、前置知识

在 JVM 中,Java对象保存在堆中时,由以下三部分组成:

  • 对象头(Object Header):包括关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。由两个词mark wordklass pointer组成,如果是数组对象的话,还会有一个length field

    • mark word:通常是一组位域,用于存储对象自身的运行时数据,如hashCode、GC分代年龄、锁同步信息等等。占用64个比特,8个字节。
    • klass pointer:类指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。占用64个比特,8个字节。开启压缩类指针后,占用32个比特,4个字节。
  • 实例数据(Instance Data):存储了代码中定义的各种字段的内容,包括从父类继承下来的字段和子类中定义的字段。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等。为了提高存储空间的利用率,这部分数据的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。
  • 对齐填充(Padding):对象可以有对齐数据也可以没有。默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的整数倍。如果一个对象的对象头和实例数据占用的总大小不到8字节的整数倍,则以此来填充对象大小至8字节的整数倍。

为什么要对齐填充?字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。

我看到网络上有些文章把mark word称之为对象头,把java对象的内存布局分为4个部分mark word、klass pointer、instance data、padding,这很明显是没有看过官方文档的,说法并不严谨。关于对象头,可以在Hotspot官方文档找到下面的描述:

四、详细解释

因为第二种解释包含了第一种解释,所以我们分析第二种解释。

1.Object o

在Hotspot实现的64位虚拟机中,原本情况下,它内部的一个引用,就应该占64个比特,也就是8个字节。什么叫引用啊?上面那个变量小o,就叫引用,也叫普通对象指针(别说什么java里没有指针,什么引用和指针不一样。我不想去争论这个)。但是,在第二种解释中我们说了,普通对象指针,占4个字节,怎么又成8个字节了,怎么回事呢?

这是因为Hotspot实现的64位虚拟机,默认会开启压缩普通对象指针,会把8个字节的对象引用,压缩成4个字节。

Object o占用大小分为两种情况:

  • 未开启压缩对象指针

    8字节

  • 开启压缩对象指针(默认是开启的)

    4字节

2.new Object()

同样的,在Hotspot实现的64位虚拟机中,原本情况下,类指针应该占64个比特,也就是8个字节。但因为Hotspot实现的64位虚拟机,默认会开启压缩类指针(和压缩对象指针不一样),而类指针就在Klass Pointer中存储着,所以会把Klass Pointer压缩成4个字节。

new Object()占用大小分为两种情况:

  • 未开启压缩类指针

    8字节(Mark Word) + 8字节(Klass Pointer) = 16字节

  • 开启压缩类指针(默认是开启的)

    8字节(Mark Word) + 4字节(Klass Pointer) + 4字节(Padding) = 16字节

五、验证

光说不练假把式,实践出真知,上面的只是理论,我们来实际验证下,是不是真的是这样。

1.验证默认开启压缩

首先,我们来看下,JDK8 Hotspot实现64位虚拟机,是不是会默认开启压缩类指针压缩对象指针

win + R,输入cmd,敲入下面的命令java -version,相信大家对这个命令很熟悉了,查看java版本

接下来我们加个参数-XX:+PrintCommandLineFlags,这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值,注意看下面两个参数

-XX:+UseCompressedClassPointers:使用压缩类指针

-XX:+UseCompressedOops:使用压缩普通对象指针

可以看到,这两个配置是默认开启的。

注意:32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。

什么是oop?

这参数后面的oop可不是面向对象编程Object Oriented Programming的意思,而是普通对象指针Ordinary Object Pointer。

启用UseCompressedOops后,会压缩的对象:

  • 每个Class的属性指针(静态成员变量);
  • 每个对象的属性指针;
  • 普通对象数组的每个元素指针。

当然,压缩也不是所有的指针都会压缩,对一些特殊类型的指针,JVM是不会优化的,例如指向PermGen的Class对象指针、本地变量、堆栈元素、入参、返回值和NULL指针不会被压缩。

关于UseCompressedClassPointers和UseCompressedOops

这样一看,好像UseCompressedOops对Object的内存布局并没有影响,其实不然,开启UseCompressedOops,默认会开启UseCompressedClassPointers,会压缩klass pointer这部分的大小,由8字节压缩至4字节,间接的提高内存的利用率。关闭UseCompressedOops默认会关闭UseCompressedClassPointers。

如果开启UseCompressedClassPointers,根据上面的条件,结果跟只开启UseCompressedOops一样,会在内存中消耗20个字节,o指针占4个字节,Object对象占16个字节。

如果关闭UseCompressedClassPointers,根据上面的条件,UseCompressedOops还是会开启,会在内存中消耗20个字节,o指针占4个字节,Object对象占16个字节。

如果开启类指针压缩,+UseCompressedClassPointers,并关闭普通对象指针压缩,-UseCompressedOops,此时会报错,UseCompressedClassPointers requires UseCompressedOops。因为UseCompressedClassPointers的开启是依赖于UseCompressedOops的开启。

2.验证实例对象布局大小

上面已经看到,JVM默认开启了压缩类指针压缩普通对象指针,那么在这个情况下,new Object()是否真的是8字节(Mark Word) + 4字节(Klass Pointer) + 4字节(Padding) = 16字节呢?

还好 openjdk 给我们提供了一个工具包,可以用来获取对象的信息和虚拟机的信息,我们只需引入 jol-core 依赖,如下:

  1. <dependency>
  2. <groupId>org.openjdk.jol</groupId>
  3. <artifactId>jol-core</artifactId>
  4. <version>0.14</version>
  5. </dependency>

jol-core 常用的三个方法:

  • ClassLayout.parseInstance(object).toPrintable():查看对象内部信息.
  • GraphLayout.parseInstance(object).toPrintable():查看对象外部信息,包括引用的对象.
  • GraphLayout.parseInstance(object).totalSize():查看对象总大小.

简单对象

为了简单化,我们不用复杂的对象,自己创建一个类 Test01,先看无属性字段的时候

  1. public class Test01 {
  2. public static void main(String[] args) {
  3. Test01 t = new Test01();
  4. System.out.println(ClassLayout.parseInstance(t).toPrintable());
  5. }
  6. }

通过 jol-core 的 api,我们将对象的内部信息打印出来:

可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 这几个名词头,它们的含义分别是

  • OFFSET:偏移地址,单位字节;
  • SIZE:占用的内存大小,单位为字节;
  • TYPE DESCRIPTION:类型描述,其中object header为对象头;
  • VALUE:对应内存中当前存储的值,二进制32位;

同时可以看到,t实例对象共占据16Byte,object header占据12Byte,其中mark word占8Byte,klass pointer占4Byte,padding占4Byte。

如果我把压缩普通对象指针的参数去掉呢?可以通过配置vm参数关闭压缩类指针,-XX:-UseCompressedClassOops。我们再看看结果:

可以看到,对象头所占用的内存大小变为16Byte,其中mark word占8Byte,klass pointer占8Byte,无padding。

至此,已经证明了我们上面的结论是正确的。

有成员变量的对象

我们现在再给Test01类里加4个成员变量,开启指针压缩,看看它的布局吧

  1. public class Test01 {
  2. String a = "a";
  3. int b = 1;
  4. boolean c = false;
  5. char d = 'd';
  6. public static void main(String[] args) {
  7. Test01 t = new Test01();
  8. System.out.println(ClassLayout.parseInstance(t).toPrintable());
  9. }
  10. }

可以看到,对象大小变成了24Byte,其中mark word占8Byte,klass pointer占4Byte,int占4Byte,char占2Byte,boolean占1Byte,padding占1Byte,String类型的变量a占4Byte,也验证了我们上面说的“为了提高存储空间的利用率,这部分数据的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响”,可以看到内存中的布局顺序确实和我们定义的不一样。

此时我再关闭两个指针压缩,再看看布局变化:

可以看到,对象总大小变成了32Byte,和开启压缩指针相比,klass pointer大了4Byte,String类型的变量a大了4Byte。符合我们上面的结论。

Object o = new Object()占多少个字节?-对象的内存布局的更多相关文章

  1. 面试官问我:Object o = new Object() 占用了多少个字节?

    小小面试一下 前言蜜语 最近马师傅火的不要不要的,虽然没有抢到耗子尾汁的商标注册权,但是必须得蹭一波马师傅的热度,下面就是闪电五连鞭的教学环节,你准备好了吗! 在正式内容开始前先甩两篇关于类加载机制和 ...

  2. Object、Function、String、Array原生对象扩展方法

    JavaScript原生对象的api有些情况下使用并不方便,考虑扩展基于Object.Function.String.Array扩展,参考了prototype.js的部分实现,做了提取和修改,分享下: ...

  3. Java中泛型Class<T>、T与Class<?>、 Object类和Class类、 object.getClass()和Object.class

    一.区别 单独的T 代表一个类型(表现形式是一个类名而已) ,而 Class<T>代表这个类型所对应的类(又可以称做类实例.类类型.字节码文件), Class<?>表示类型不确 ...

  4. Object obj=new Object()的内存引用

    Object obj=new Object(); 一句很简单的代码,但是这里却设计Java栈,Java堆,java方法去三个最重要的内存区域之间的关联. 假设这句代码出现在方法体中. 1.Object ...

  5. host Object和native Object的区别

    Native Object: JavaScript语言提供的不依赖于执行宿主的对象,其中一些是内建对象,如:Global.Math:一些是在脚本运行环境中创建来使用的,如:Array.Boolean. ...

  6. 请问utf-8的中文是一个汉字占三个字节长度吗?

    这是个好问题,可以当作一个笔试题.先从字符编码讲起. 1.美国人首先对其英文字符进行了编码,也就是最早的ascii码,用一个字节的低7位来表示英文的128个字符,高1位统一为0: 2.后来欧洲人发现尼 ...

  7. Intent传递List和Object和List<Object>

    一.传递List 传递List<String>的方法 小技巧,List<object> 可以使用json 转为 List<string>,就可以使用 List< ...

  8. JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别

    ECMAScript将对象的属性分为两种:数据属性和访问器属性.每一种属性内部都有一些特性,这里我们只关注对象属性的[[Enumerable]]特征,它表示是否通过 for-in 循环返回属性,也可以 ...

  9. iOS 杂笔-21(self.name = “object” 和 _name =”object” 有什么不同?)

    iOS 杂笔-21(self.name = "object" 和 _name ="object" 有什么不同?) 问题如题,这是考察对属性与变量的了解而已. s ...

随机推荐

  1. CloudQuery v1.2.1 版本发布

    欢迎来到 CloudQuery v1.2.1 版本发布会. 上次 v1.2.0 版本发布收到广大朋友们的热烈反响,大家提出了很多宝贵建议,揪出了不少 Bug.在此,我们表示由衷感谢.问题和建议我们都会 ...

  2. 工具类:每次随机生成有销售库存有实际库存的1个店铺商品和对应的2个店铺商品sku

    # coding:utf-8 # @fileName :2.每次随机生成有销售库存有实际库存的1个店铺商品和对应的2个店铺商品sku.py # @createTime :2020/4/4 10:33 ...

  3. GridSearchCV网格搜索得到最佳超参数, 在K近邻算法中的应用

    最近在学习机器学习中的K近邻算法, KNeighborsClassifier 看似简单实则里面有很多的参数配置, 这些参数直接影响到预测的准确率. 很自然的问题就是如何找到最优参数配置? 这就需要用到 ...

  4. Java基础语法:final修饰符

    一.final类 描述: 用'final'修饰的类不能被继承,没有子类. 例如,我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经将String类定义为'final ...

  5. 使paramiko库执行命令时,在给定的时间强制退出

    原因: 使用paramiko库ssh连接到远端云主机上时,非常偶现卡死现象,连接无法退出(可以是执行命令时云主机重启等造成).需要给定一段时间,不管命令执行是否卡住,都退出连接,显示命令执行超时错误. ...

  6. jdk 集合大家族之Map

    jdk 集合大家族之Map 前言: 之前章节复习了Collection接口相关,此次我们来一起回顾一下Map相关 .本文基于jdk1.8. 1. HashMap 1.1 概述 HashMap相对于Li ...

  7. WDN302国产化网络存储控制模块

    WDN302是一款网络存储控制模块,实现对NAS和IP-SAN的混合支持,通过以太网对海量存储的访问,实现数据的存储.共享.恢复和防丢失. 飞腾 FT1500A/16处理器,主频 1.5GHz: 支持 ...

  8. [ONTAK2010] Peaks 加强版

    [ONTAK2010] Peaks 加强版 题目大意:原题变为强制在线查询 Solution 读入山高,排序后依然建立树链,初始化并查集,初始化重构树新节点标号为\(n+1\) 读入边,按照边权从小到 ...

  9. yolo训练数据集

    最近了解了下yolov3的训练数据集部分,总结了以下操作步骤:(基于pytorch框架,请预先装好pytorch的相关组件) 1.下载ImageLabel软件对图片进行兴趣区域标记,每张图片对应一个x ...

  10. Flask模板注入

    Flask模板注入 Flask模板注入漏洞属于经典的SSTI(服务器模板注入漏洞). Flask案例 一个简单的Flask应用案例: from flask import Flask,render_te ...