「阿里巴巴」程序员最爱new对象,可是,你真的知道一个对象占用多少内存吗?( 二 )


<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>

我们先来看看 , 如果只是新建一个普通的类 , 什么属性也不添加 , 占用的空间是多少?
public class NullObject {

按照我们之前的Java对象内存模型分析 , 一个空对象 , 那就是只有一个对象头部 , 在指针压缩的条件下会占用 96 bit , 也就是12byte 。
运行工具查看空间占用
public static void main(String[
args) {
System.out.println(ClassLayout.parseInstance(new NullObject()).toPrintable());

上面这行代码会解析你新建一个NullObject对象 , 占用了多少内存 。 我们执行看看结果如何:
这里我们发现结果显示:Instance size:16 bytes结果就是16字节 , 我们之前预测的12字节不一样 , 为什么会这样呢?我们看到上图中有3行 object header , 每个占用4字节 , 所以头部就是12字节 , 这里和我们的计算是一致的 , 最后一行是虚拟机填充的4字节 , 那为什么虚拟机要填充4个字节呢?
内存对齐想要知道为什么虚拟机要填充4个字节 , 我们需要了解什么是内存对齐?
我们程序员看内存是这样的:
上图表示一个坑一个萝卜的内存读取方式 。 但实际上 CPU 并不会以一个一个字节去读取和写入内存 。 相反 CPU 读取内存是一块一块读取的 , 块的大小可以为 2、4、6、8、16 字节等大小 。 块大小我们称其为内存访问粒度 。 如下图:
假设一个32位平台的 CPU , 那它就会以4字节为粒度去读取内存块 。 那为什么需要内存对齐呢?主要有两个原因:
· 平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据 。 例如:特定的硬件平台只允许在特定地址获取特定类型的数据 , 否则会导致异常情况 。
· 性能原因:若访问未对齐的内存 , 将会导致 CPU 进行两次内存访问 , 并且要花费额外的时钟周期来处理对齐及运算 。 而本身就对齐的内存仅需要一次访问就可以完成读取动作 。
我用图例来说明 CPU 访问非内存对齐的过程:
在上图中 , 假设CPU 是一次读取4字节 , 在这个连续的8字节的内存空间中 , 如果我的数据没有对齐 , 存储的内存块在地址1 , 2 , 3 , 4中 , 那CPU的读取就会需要进行两次读取 , 另外还有额外的计算操作:
1. CPU 首次读取未对齐地址的第一个内存块 , 读取 0-3 字节 。 并移除不需要的字节 0 。
2. CPU 再次读取未对齐地址的第二个内存块 , 读取 4-7 字节 。 并移除不需要的字节 5、6、7 字节 。
3. 合并 1-4 字节的数据 。
4. 合并后放入寄存器 。
所以 , 没有进行内存对齐就会导致CPU进行额外的读取操作 , 并且需要额外的计算 。 如果做了内存对齐 , CPU可以直接从地址0开始读取 , 一次就读取到想要的数据 , 不需要进行额外读取操作和运算操作 , 节省了运行时间 。 我们用了空间换时间 , 这就是为什么我们需要内存对齐 。
回到Java空对象填充了4个字节的问题 , 因为原字节头是12字节 , 64位机器下 , 内存对齐的话就是128位 , 也就是16字节 , 所以我们还需要填充4个字节 。
非空对象占用内存计算我们知道了一个空对象是占用16字节 , 那么一个非空对象究竟占用多少字节呢?我们还是写一个普通类来验证下:
public class TestNotNull {    private NullObject nullObject=new NullObject();    private int a;

这个演示类中引入了别的对象 , 我们知道int类型是占用4个字节NullObject对象占用16字节 , 对象头占12字节 , 还有一个很重要的情况 NullObject在当前这个类中是一个引用 , 所以不会存真正的对象 , 而只存引用地址 , 引用地址占4字节 , 所以总共就是12+4+4=20字节 , 内存对齐后就是24字节 。 我们来验证下是不是这个结果: