在进行 JVM 调优时,我们经常关注 JVM 各个区域大小以及相关参数,从而进行特定的优化,在一次排查内存溢出问题时我不禁想到一个问题,一个 Java 对象到底占用多大内存?下面我们就来分析验证下。
Java 对象内存结构
在 JVM 中,Java 对象都是在堆内存上分配的,想要分析出 Java 对象内存占用,首先要了解 Java 对象内存结构,一个 Java 对象内存占用由三部分组成:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。
对象头(Header)
对象头的组成
虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如 hashCode 、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在 32 位和 64 位的虚拟机(未开启指针压缩)中分别为 4B 和 8B ,官方称之为 ”Mark Word”。
对象的另一部分是类型指针(kclass),即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。另外如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是从数组的元数据中却无法确定数组的大小。同样,这部分数据的长度在 32 位和 64 的虚拟机(未开启指针压缩)中分别为 4B 和 8B。
指针压缩
从 JDK 1.6 update14 开始,64 bit JVM 正式支持了 -XX:+UseCompressedOops 这个可以压缩指针,起到节约内存占用的新参数。
如果 UseCompressedOops 是打开的,则以下对象的指针会被压缩:
所有对象的 klass 属性
所有对象指针实例的属性
所有对象指针数组的元素(objArray)
由此我们可以计算出对象头大小:
32位虚拟机对象头大小= Mark Word(4B)+ kclass(4B) = 8B
64位虚拟机对象头大小= Mark Word(8B)+ kclass(4B) = 12B
实例数据
一个 Java 对象中的实例数据可能包括两种,一是 8 种基本类型,二是实例数据也是一个对象,说到这里很多人可能有个误区:
基本类型?基本类型不是在栈上分配内存的吗?怎么要计算到分配在堆内存上对象的大小里面去?
基本类型在栈上分配内存?其实并不是,所谓“栈内存保存基本类型以及对象的引用(reference),堆内存保存对象” 只是一句不严谨的话,实际仔细研究起来,栈内存(更专业的术语叫做堆栈)作为虚拟机作为方法调用和方法执行的数据结构,可能保存五种信息:
局部变量表
操作数栈
动态链接
方法返回地址
附加信息
其中局部变量表中存储了方法中的局部变量,可能为 8 种基本类型或者 reference
也就是说,栈内存中保存的基本类型,都是方法中的局部变量,而如果基本类型作为对象的实例变量,是在堆上分配空间的,此外,如果实例变量被final修饰,则既不在栈也不在堆上分配空间,而是分配到常量池里面。
8 种基本类型和 reference 大小在虚拟机上都是固定的,见下表
Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
Reference 4
对齐填充(Padding)
由于虚拟机内存管理体系要求 Java 对象内存起始地址必须为 8 的整数倍,换句话
一个Java对象到底占用多大内存?
最新推荐文章于 2024-04-17 21:00:00 发布
本文详细分析了Java对象在JVM中的内存占用,包括对象头(Mark Word和类型指针)、实例数据以及对齐填充。讨论了64位虚拟机的指针压缩,并给出了计算对象大小的理论方法。通过Instrumentation工具验证了理论计算,展示了如何使用javaagent技术计算对象的实际大小。
摘要由CSDN通过智能技术生成