我们在如何获取一个Java对象所占内存大小的文章中写了一个获取Java对象所占内存大小的工具类(ObjectSizeFetcher),那么接下来,我们使用这个工具类来看一下Java中各种类型的对象所占内存的大小

基本类型

基本类型的内存占用情况如下表:

基本类型 内存大小(单位:字节)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

以上基本类型所占内存大小是Java规定的,引用类型所占内存大小就不是确定的了,接下来我们看下引用类型所占内存大小,我们先从Java单个对象内存布局开始

Java单个对象内存布局

对象头

在Java中,每一个对象都包含对象头,对象头包含两类数据:存储对象自身的运行时数据和类型指针数据

  1. 存储对象自身的运行时数据,Mark Word(在32位和64位操作系统上长度分别为4字节和8字节),包含如下信息:
    1. 对象hashCode
    2. 对象GC分代年龄
    3. 锁状态标志(轻量级锁、重量级锁)
    4. 线程持有的锁(轻量级锁、重量级锁)
    5. 偏向锁相关
  2. 类型指针:对象指向类元数据的指针(32位操作系统-->4字节,64位操作系统-->8字节(未开启压缩指针),4字节(开启压缩指针))
    • JVM通过这个指针来确定这个对象是哪个类的实例(根据对象确定其Class的指针)

所以在32位操作系统上,一个Java对象的对象头所占内存大小为8字节

而在64位操作系统上:

  • 如果未开启压缩指针,那么对象头的大小为16字节
  • 如果开启压缩指针,那么对象头的大小为12字节

JVM通过参数UseCompressedOops来控制是否开启压缩指针的功能,默认是开启,我们来看一下这个参数。

我们在如何获取一个Java对象所占内存大小的最后留下了一个问题,那就是new Point()这个对象所占内存大小为什么是24字节呢?以下是Point的代码:

public class Point {
private int x; // 4字节
private int y; // 4字节 public static void main(String [] args) {
System.out.println(ObjectSizeFetcher.sizeOf(new Point()));
}
}

  这个Point类有两个属性xy,都是int类型的,而int类型所占内存大小是4字节,那么两个int类型的属性所占大小为8字节,那么24 - 8 = 16字节是什么所占的内存呢?

不开启指针压缩功能

当我们执行下面的命令的时候:

## 不开启指针压缩功能
java -XX:-UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.Point

new Point()这个对象的大小是24字节

因为没有开启指针压缩功能,所以这个时候的对象头的大小是16字节(注意:我的电脑是64位操作系统)。那么24 - 8 = 16字节就是对象头所占的内存大小

开启指针压缩功能

当我们执行下面的命令的时候:

## 开启指针压缩功能
java -XX:+UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.Point

  new Point()这个对象的大小还是24字节

因为开启了指针压缩功能,所以这个时候的对象头的大小是12字节。那么24 - 8 = 16字节中的16字节除了包含了12字节的对象头,还有4字节多,这个4字节就是对齐填充(padding)的。

对齐填充:JVM要求对象的大小必须是8的整数倍,若不是,需要补位对齐。

在开启指针压缩功能的时候,对象头大小是12字节 + 2个int类型属性大小8字节 = 20字节,因为20不是8的倍数,所以需要对齐填充4个字节,即24字节

Java单个对象内存布局总结:

  • Java单个对象所占内存大小等于:对象头所占内存大小 + 对象实例属性数据所占内存大小 + 对齐填充所占内存大小
  • 对象头包含存储对象自身的运行时数据和类型指针数据两类数据。在64位操作系统中,如果开启指针压缩功能的话,对象头所占内存大小为12字节;如果没有开启指针压缩功能的话,对象头所占内存大小为16字节
  • 对齐填充:JVM要求对象的大小必须是8的整数倍,若不是,需要补位对齐。

static修饰的属性

我们在Point中增加一个static修饰的变量,如下代码:

public class Point {
private int x;
private int y; public static long id = 3000L; // 增加一个static修饰的变量 public static void main(String [] args) {
System.out.println(ObjectSizeFetcher.sizeOf(new Point()));
}
}

  当我们执行下面的命令的时候:

## 不开启指针压缩功能
java -XX:-UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.Point ## 开启指针压缩功能
java -XX:+UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.Point

  得到的结果如下:

new Point()这个对象所占的内存大小还是24字节,证明static变量属于类,不属于实例,存放在全局数据段,普通变量才纳入Java对象占用空间的计算。

引用类型

引用类型在32位操作系统上每个占用4字节

在64位操作系统上:

  • 没有开启指针压缩功能的话占用8字节
  • 开启指针压缩功能的话占用4字节

我们写一个名为RefTypeSizer的类,其内容为:

class Person {
} public class RefTypeSizer {
// 这个是引用类型
private Person person; public static void main(String[] args) throws IllegalAccessException {
System.out.println("对象new RefTypeSizer()所占内存大小:" + ObjectSizeFetcher.sizeOf(new RefTypeSizer()) + "字节");
}
}

  然后,我们重新打包,然后先执行下面的命令:

## 不开启指针压缩功能
java -XX:-UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.RefTypeSizer

  得到的对象new RefTypeSizer()所占内存大小为24字节

因为这个时候没有开启指针压缩功能,所以对象头大小为16字节,引用类型Person person所占内存为8字节,所以加起来大小为16 + 8 = 24字节

现在,我们再来打开指针压缩功能,如下命令:

## 开启指针压缩功能
java -XX:+UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.RefTypeSizer

  

得到的对象new RefTypeSizer()所占内存大小为16字节

因为这个时候开启了指针压缩功能,所以对象头大小为12字节,引用类型Person person所占内存为4字节,所以加起来大小为12 + 4 = 16字节

数组

在64位操作系统上,数组对象的对象头占用24字节,启用指针压缩功能后占用16字节,之所以比普通对象占用内存多是因为数组需要额外的空间存储数组的长度。

我们看如下计算数组长度的代码:

public class ArraySizer {
public static void main(String[] args) {
System.out.println("new Integer[0]所占内存大小为:" + ObjectSizeFetcher.sizeOf(new Integer[0]) + "字节");
System.out.println("new Integer[1]所占内存大小为:" + ObjectSizeFetcher.sizeOf(new Integer[1]) + "字节");
System.out.println("new Integer[2]所占内存大小为:" + ObjectSizeFetcher.sizeOf(new Integer[2]) + "字节");
System.out.println("new Integer[3]所占内存大小为:" + ObjectSizeFetcher.sizeOf(new Integer[3]) + "字节");
System.out.println("new Integer[4]所占内存大小为:" + ObjectSizeFetcher.sizeOf(new Integer[4]) + "字节");
}
}

  然后,我们重新打包,然后先执行下面的命令:

## 不开启指针压缩功能
java -XX:-UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.ArraySizer

  得到结果如下:

我们可以看到new Integer[0]的大小为24字节,因为数组长度为0,所以这个数组的大小就是数组对象头的大小,又因为没有开启指针压缩功能,所以数组对象头大小为24字节,其他长度数组所占内存解释:

  • new Integer[1]的大小为:对象头24字节 + 1个引用类型大小8字节 = 32字节
  • new Integer[2]的大小为:对象头24字节 + 2个引用类型大小16字节 = 40字节
  • new Integer[3]的大小为:对象头24字节 + 3个引用类型大小24字节 = 48字节
  • new Integer[4]的大小为:对象头24字节 + 4个引用类型大小32字节 = 56字节
  • new Integer[]{2, 3, 4, 5}的大小位:new Integer[4]的大小56字节 + 4 * (Integer对象头16字节 + Integer中int类型属性大小4字节 + 对齐填充4字节) = 152字节

接着我们开启指针压缩功能,执行如下命令:

## 开启指针压缩功能
java -XX:+UseCompressedOops -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar com.twq.ArraySizer

  得到结果如下:

我们可以看到new Integer[0]的大小为16字节,因为数组长度为0,所以这个数组的大小就是数组对象头的大小,又因为开启了指针压缩功能,所以数组对象头大小为16字节,其他长度数组所占内存解释:

  • new Integer[1]的大小为:对象头16字节 + 1个引用类型大小4字节 + 对齐补充4字节 = 24字节
  • new Integer[2]的大小为:对象头16字节 + 2个引用类型大小8字节 = 24字节
  • new Integer[3]的大小为:对象头16字节 + 3个引用类型大小12字节 + 对齐补充4字节 = 32字节
  • new Integer[4]的大小为:对象头16字节 + 4个引用类型大小16字节 = 32字节
  • new Integer[]{2, 3, 4, 5}的大小位:new Integer[4]的大小32字节 + 4 * (Integer对象头12字节 + Integer中int类型属性大小4字节) = 96字节

总结

Java单个对象内存布局总结:

  • Java单个对象所占内存大小等于:对象头所占内存大小 + 对象实例属性数据所占内存大小 + 对齐填充所占内存大小
  • 对象头包含存储对象自身的运行时数据和类型指针数据两类数据。在64位操作系统中,如果开启指针压缩功能的话,对象头所占内存大小为12字节;如果没有开启指针压缩功能的话,对象头所占内存大小为16字节
  • 对齐填充:JVM要求对象的大小必须是8的整数倍,若不是,需要补位对齐。
  • static变量属于类,不属于实例,存放在全局数据段,普通变量才纳入Java对象占用空间的计算
  • 引用类型在32位操作系统上每个占用4字节,在64位操作系统上:
    • 没有开启指针压缩功能的话占用8字节
    • 开启指针压缩功能的话占用4字节
  • 在64位操作系统上,数组对象的对象头占用24字节,启用指针压缩功能后占用16字节,之所以比普通对象占用内存多是因为数组需要额外的空间存储数组的长度。

以上是单个简单的Java对象所占内存的大小的计算,对于复杂的Java对象所占内存的大小的计

Java单个对象内存布局.md的更多相关文章

  1. Java对象内存布局

    本文转载自Java对象内存布局 导语 首先直接抛出问题 Unsafe.getInt(obj, fieldOffset)中的fieldOffset是什么, 类似还有compareAndSwapX(obj ...

  2. 图文详解Java对象内存布局

    作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了.对象的创建方式虽然有很多,可以通过new.反射.clone.反序列化等不同方式来创建 ...

  3. HotSpot虚拟机对象探秘(对象创建,对象内存布局,对象访问定位)

    以常用的HotSpot虚拟机和JAVA内存区域堆为例,探讨对象的创建,对象的内存布局以及对象的访问定位 一.对象的创建 1)类加载:虚拟机遇到一条new指令时,先检测这个指令的参数能否在常量池中定位到 ...

  4. C++对象内存布局测试总结

    C++对象内存布局测试总结 http://hi.baidu.com/����/blog/item/826d38ff13c32e3a5d6008e8.html 上文是半年前对虚函数.虚拟继承的理解.可能 ...

  5. JVM-对象及对象内存布局

    目录 前言 类与对象 对象类二分模型 对象 对象内存布局 JOL工具 对象头 Mark Word 类型句柄 对象头与锁膨胀 无锁 偏向锁 轻量级锁 重量级锁 重量级锁降级 实例数据 填充 对象生命周期 ...

  6. JVM之对象创建、对象内存布局、对象访问定位

    对象创建 类加载过后可以直接确定一个对象的大小 对象栈上分配是通过逃逸分析判定.标量替换实现的,即把不存在逃逸的对象拆散,将成员变量恢复到基本类型,直接在栈上创建若干个成员变量 选择哪种分配方式由Ja ...

  7. JVM 系列(4)一看就懂的对象内存布局

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  8. 图说C++对象模型:对象内存布局详解

    0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...

  9. c++ 对象内存布局详解

    今天看了的,感觉需要了解对象内存的问题.参考:http://blog.jobbole.com/101583/ 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个 ...

随机推荐

  1. ENSP静态链路聚合实验配置要点及实例

    链路聚合分为:静态链路聚合.动态链路聚合链路聚合的负载分担模式:对于二层数据流,系统会根据MAC地址(源mac地址和目的mac地址来计算),而对于三层数据流则会根据ip地址来进行负载分担计算. 静态链 ...

  2. TCP粘包和拆包的定义,产生的原因以及解决方案

    TCP粘包:指发送方发送的若干数据包在接收方接收时粘成一团,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾 产生的原因: 1.发送方的原因:TCP默认使用Nagle算法,而Nagle算法主要做两件 ...

  3. [转帖]都在说DCEP,央行数字货币究竟跟你有什么关系?

    都在说DCEP,央行数字货币究竟跟你有什么关系? https://kuaibao.qq.com/s/20191104A0G1D300?refer=spider   黄奇帆指出,DCEP 使得交易环节对 ...

  4. int and Integer

    int和Integer的区别 1.Integer是int的包装类,int则是java的一种基本数据类型 2.Integer变量必须实例化后才能使用,而int变量不需要 3.Integer实际是对象的引 ...

  5. 阅读笔记——《How a Facebook rejection pushed me to start and grow a profitable business in 12 months》

    阅读笔记——<How a Facebook rejection pushed me to start and grow a profitable business in 12 months> ...

  6. Dubbo学习摘录(一)

    Dubbo基于自定义配置,实现自己的Bean. 通常,完成一个自定义配置需要以下步骤: 设计配置属性和JavaBean: 编写XSD文件: 编写NamespaceHandler和BeanDefinit ...

  7. vmware下的linux没有网络问题解决思路

    这里几天一直在搞vm下的linux主机的网络问题,这里做个总结 这里使用的NAT连接方式 1.首先保证本机联网正常 2.检查虚拟机相应的服务(VMware NET Service 和 VMware W ...

  8. BUAAOO-Third-Summary

    目录 从DBC到JML SMT solver 使用 JML toolchain的可视化输出 和我的测试结果 规格的完善策略 架构设计 debug情况 心得体会 一.从DBC到JML 契约式设计(Des ...

  9. cgal的安装、编译

    1.下载地址 https://www.cgal.org/2019/03/29/cgal414/ https://github.com/CGAL/cgal/releases/tag/releases%2 ...

  10. 将H5页面打包成安卓原生app

    第一步:下载HBuilderX,新建项目选择5+App新建一个空项目如下图 新建后项目目录结构如下图 第二步,将你要打包成安卓app的文件打包,最后生成的文件目录如下图 1.打包完成后,将对应文件内容 ...