你写的Java对象究竟占多少内存?

概述Java 作为一个面向对象语言 , 给我们带来了多态 , 继承 , 封装等特性 , 使得我们可以利用这些特性很轻松的就能构建出易于扩展 , 易于维护的代码 。 作为一个Javaer , 天天搞“对象” , 那你写的对象究竟占用了多少内存呢?我们来看看你的“对象”是如何“败家”的 。
本文环境:jdk1.8_64
Java 对象头内存模型我们先来看看 , 一个Java 对象的内存模型是怎么样的?由于我们的虚拟机是分为32位和64位 , 那肯定它们的模型也是有区别的 , 下面我列出列32位虚拟机和64位虚拟机下的Java对象头内存模型 。
你写的Java对象究竟占多少内存?文章插图
你写的Java对象究竟占多少内存?文章插图
你写的Java对象究竟占多少内存?文章插图
因为笔者的本地环境是jdk1.8,64位虚拟机 , 这里我以64位虚拟机(开启指针压缩)来分析 , 因为默认情况下 , jdk1.8 在64位虚拟机默认开启指针压缩 。
Java 对象头主要包括两部分 , 第一部分就是 Mark Word , 这也是 Java 锁实现原理中重要的一环 , 另外一部分是 Klass Word 。
Klass Word 这里其实是虚拟机设计的一个oop-klass model模型 , 这里的OOP是指Ordinary Object Pointer(普通对象指针) , 看起来像个指针实际上是藏在指针里的对象 。 而 klass 则包含 元数据和方法信息 , 用来描述 Java 类 。 它在64位虚拟机开启压缩指针的环境下占用 32bits 空间 。
Mark Word 是我们分析的重点 , 这里也会设计到锁的相关知识 。 Mark Word 在64位虚拟机环境下占用 64bits 空间 。 整个Mark Word的分配有几种情况:

  1. 未锁定(Normal): 哈希码(identity_hashcode)占用31bits , 分代年龄(age)占用4 bits , 偏向模式(biased_lock)占用1 bits , 锁标记(lock)占用2 bits , 剩余26bits 未使用(也就是全为0)
  2. 可偏向(Biased): 线程id 占54bits , epoch 占2 bits , 分代年龄(age)占用4 bits , 偏向模式(biased_lock)占用1 bits , 锁标记(lock)占用2 bits , 剩余 1bit 未使用 。
  3. 轻量锁定(Lightweight Locked): 锁指针占用62bits , 锁标记(lock)占用2 bits 。
  4. 重量级锁定(Heavyweight Locked):锁指针占用62bits , 锁标记(lock)占用2 bits 。
  5. GC 标记:标记位占2bits , 其余为空(也就是填充0)
以上就是我们对Java对象头内存模型的解析 , 只要是Java对象 , 那么就肯定会包括对象头 , 也就是说这部分内存占用是避免不了的 。 所以 , 在笔者64位虚拟机 , Jdk1.8(开启了指针压缩)的环境下 , 任何一个对象 , 啥也不做 , 只要声明一个类 , 那么它的内存占用就至少是96bits , 也就是至少12字节 。
验证模型我们来写点代码来验证一下上述的内存模型 , 这里推荐openjdk的jol工具 , 它可以帮助你查看对象内存的占用情况 。
首先添加maven依赖
org.openjdk.joljol-core0.10我们先来看看 , 如果只是新建一个普通的类 , 什么属性也不添加 , 占用的空间是多少?
/** * @description: * @author: luozhou * @create: 2020-02-26 10:00 **/public class NullObject {}按照我们之前的Java对象内存模型分析 , 一个空对象 , 那就是只有一个对象头部 , 在指针压缩的条件下会占用 96 bit , 也就是12byte 。
运行工具查看空间占用
public static void main(String[] args) {System.out.println(ClassLayout.parseInstance(new NullObject()).toPrintable());}上面这行代码会解析你新建一个NullObject对象 , 占用了多少内存 。 我们执行看看结果如何:
你写的Java对象究竟占多少内存?文章插图
这里我们发现结果显示:Instance size:16 bytes,结果就是16字节 , 我们之前预测的12字节不一样 , 为什么会这样呢?我们看到上图中有3行 object header , 每个占用4字节 , 所以头部就是12字节 , 这里和我们的计算是一致的 , 最后一行是虚拟机填充的4字节 , 那为什么虚拟机要填充4个字节呢?
内存对齐想要知道为什么虚拟机要填充4个字节 , 我们需要了解什么是内存对齐?
我们程序员看内存是这样的:
你写的Java对象究竟占多少内存?文章插图
上图表示一个坑一个萝卜的内存读取方式 。 但实际上 CPU 并不会以一个一个字节去读取和写入内存 。 相反 CPU 读取内存是一块一块读取的 , 块的大小可以为 2、4、6、8、16 字节等大小 。 块大小我们称其为内存访问粒度 。 如下图: