Java 做为一个面向对象语言,给咱们带来了多态,继承,封装等特性,使得咱们能够利用这些特性很轻松的就能构建出易于扩展,易于维护的代码。做为一个Javaer
,每天搞“对象”,那你写的对象究竟占用了多少内存呢?咱们来看看你的“对象”是如何“败家”的。html
本文环境:jdk1.8_64java
想要了解Java对象究竟占用多少内存一定先要了解一个Java
对象的内存模型是怎么样的?因为咱们的虚拟机是分为32位和64位,那确定它们的模型也是有区别的,下面我列出列32位虚拟机和64位虚拟机下的Java
对象头内存模型。git
由于笔者的本地环境是jdk1.8
,64位虚拟机,这里我以64位虚拟机(开启指针压缩)来分析,由于默认状况下,jdk1.8
在64位虚拟机默认开启指针压缩。程序员
Java 对象头主要包括两部分,第一部分就是 Mark Word
,这也是 Java
锁实现原理中重要的一环,另一部分是 Klass Word
。github
Klass Word 这里实际上是虚拟机设计的一个oop-klass model
模型,这里的OOP
是指Ordinary Object Pointer
(普通对象指针),看起来像个指针其实是藏在指针里的对象。而 klass
则包含 元数据和方法信息,用来描述 Java
类。它在64位虚拟机开启压缩指针的环境下占用 32bits 空间。bash
Mark Word 是咱们分析的重点,这里也会设计到锁的相关知识。Mark Word
在64位虚拟机环境下占用 64bits 空间。整个Mark Word
的分配有几种状况:maven
identity_hashcode
)占用31bits,分代年龄(age
)占用4 bits,偏向模式(biased_lock
)占用1 bits,锁标记(lock
)占用2 bits,剩余26bits 未使用(也就是全为0)epoch
占2 bits,分代年龄(age
)占用4 bits,偏向模式(biased_lock)占用1 bits,锁标记(lock)占用2 bits,剩余 1bit 未使用。lock
)占用2 bits。lock
)占用2 bits。以上就是咱们对Java对象头内存模型的解析,只要是Java对象,那么就确定会包括对象头,也就是说这部份内存占用是避免不了的。因此,在笔者64位虚拟机,Jdk1.8(开启了指针压缩)的环境下,任何一个对象,啥也不作,只要声明一个类,那么它的内存占用就至少是96bits,也就是至少12字节。ide
咱们来写点代码来验证一下上述的内存模型,这里推荐openjdk的jol工具,它能够帮助你查看对象内存的占用状况。工具
首先添加maven依赖oop
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
复制代码
咱们先来看看,若是只是新建一个普通的类,什么属性也不添加,占用的空间是多少?
/**
* @description:
* @author: luozhou
* @create: 2020-02-26 10:00
**/
public class NullObject {
}
复制代码
按照咱们以前的Java对象内存模型分析,一个空对象,那就是只有一个对象头部,在指针压缩的条件下会占用 96 bits,也就是12bytes。
运行工具查看空间占用
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 读取内存是一块一块读取的,块的大小能够为 二、四、六、八、16 字节等大小。块大小咱们称其为内存访问粒度。以下图:
假设一个32位平台的 CPU,那它就会以4字节为粒度去读取内存块。那为何须要内存对齐呢?主要有两个缘由:
我用图例来讲明 CPU 访问非内存对齐的过程:
在上图中,假设CPU 是一次读取4字节,在这个连续的8字节的内存空间中,若是个人数据没有对齐,存储的内存块在地址1,2,3,4中,那CPU的读取就会须要进行两次读取,另外还有额外的计算操做:
因此,没有进行内存对齐就会致使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字节。咱们来验证下是否是这个结果:
public static void main(String[] args) {
//打印对象内存占用
System.out.println(ClassLayout.parseInstance(new TestNotNull()).toPrintable());
System.out.println("=========================");
//输出对象相关全部内存占用
System.out.println(GraphLayout.parseInstance(new TestNotNull()).toPrintable());
System.out.println("=========================");
//输出内存占用统计
System.out.println(GraphLayout.parseInstance(new TestNotNull()).toFootprint());
}
复制代码
结果以下:
咱们能够看到TestNotNull
的类占用空间是24字节,其中头部占用12字节,变量a
是int
类型,占用4字节,变量nullObject
是引用,占用了4字节,最后填充了4个字节,总共是24个字节,与咱们以前的预测一致。可是,由于咱们实例化了NullObject
,这个对象一会存在于内存中,因此咱们还须要加上这个对象的内存占用16字节,那总共就是24bytes+16bytes=40bytes。咱们图中最后的统计打印结果也是40字节,因此咱们的分析正确。
这也是如何分析一个对象真正的占用多少内存的思路,根据这个思路加上openJDK的jol工具就能够基本的掌握本身写的“对象”究竟败家了你多少内存。
本文我主要讲述了如何分析一个Java对象究竟占用多少内存空间,主要总结点以下:
1.cr.openjdk.java.net/~lfoltan/bu…
2.gist.github.com/arturmkrtch…