我们在如何获取一个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. 第2/7Beta冲刺

    1.团队成员 成员姓名 成员学号 秦裕航 201731062432(组长) 刘东 201731062227 张旭 201731062129 王伟 201731062214 2.SCRU部分 2.1各成 ...

  2. ply2obj

    """ Simple script to convert ply to obj models """ import os from argp ...

  3. 常见的几种异常类型 Exception

    常见异常类型:Java中的异常分为两大类: 1.Checked Exception(非Runtime Exception) 2.Unchecked Exception(Runtime Exceptio ...

  4. [转帖]IBM报告:多国央行考虑发行数字货币 最快5年内问世

    IBM报告:多国央行考虑发行数字货币 最快5年内问世 https://news.cnblogs.com/n/646001/ DCEP 中国央行可能是第一家发布 数字货币的央行 DCEP 是基于 UTX ...

  5. flink ETL数据处理

    Flink ETL 实现数据清洗   一:需求(针对算法产生的日志数据进行清洗拆分) 1. 算法产生的日志数据是嵌套json格式,需要拆分 2.针对算法中的国家字段进行大区转换 3.最后把不同类型的日 ...

  6. Spring Cloud Zuul源码

    一.Zuul源码分析(初始化流程.请求处理流程)

  7. java.lang.SecurityManager、java.security包

    学习java大概3年多了,一直没有好好研究过java安全相关的问题,总是会看到 SecurityManger sm = System.getSecurityManager(); if(sm!=null ...

  8. Java内存模型——方法区

    方法区(Method Area) ①      对每个加载的类型,JVM必须在方法区中存储以下类信息: 1)        这个类型的完整有效名(类型信息) 类型名称在Java类文件和JVM中都以完整 ...

  9. easyui-datagrid清空表中原有数据

    $('#dg').datagrid('loadData', { total: 0, rows: [] });

  10. 二叉树、B树、B+树、B*树、VAL树、红黑树

    二叉搜索树 每个节点只存储一个关键字, 每个节点最多有两个子节点, 左子节点存储的关键字小于本节点存储的关键字 右子节点存储的关键字大于本节点存储的关键字 搜索时,从根节点开始搜索,小于走左结点,大于 ...