java一个对象占用多少内存(java对象占用内存大小)
导语:高端面试必备:一个Java对象占用多大内存?
这个问题一般会出现在稍微高端一点的 Java 面试环节。要求面试者不仅对 Java 基础知识熟悉,更重要的是要了解内存模型。
Java 对象模型
在 Hotspot VM 中,对象在内存中的存储布局分为 3 块区域:
对象头(Header)对象头又包括三部分:MarkWord、元数据指针、数组长度。
MarkWord:用于存储对象运行时的数据,好比 HashCode、锁状态标志、GC分代年龄等。这部分在 64 位操作系统下占 8 字节,32 位操作系统下占 4 字节。元数据指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。这部分就涉及到指针压缩的概念,在开启指针压缩的状况下占 4 字节,未开启状况下占 8 字节。数组长度:这部分只有是数组对象才有,若是是非数组对象就没这部分。这部分占 4 字节。实例数据(Instance Data)用于存储对象中的各类类型的字段信息(包括从父类继承来的)
对齐填充(Padding)Java 对象的大小默认是按照 8 字节对齐,也就是说 Java 对象的大小必须是 8 字节的倍数
为何非要进行 8 字节对齐呢?
由于 CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好也是 L1 缓存行的大小。如果不进行内存对齐,则可能出现跨缓存行的情况,这叫做 缓存行污染。
由于当 obj1 对象的字段被修改后,那么CPU在访问 obj2 对象时,必须将其重新加载到缓存行,因此影响执行效率。
在Hotspot 虚拟机文档 “oops/oop.hp” 有对 Markword 字段的定义:
我们主要关注 normal object, 这种类型的 Object 的 Markword 一共是 8 个字节(64位),其中 25 位暂时没有使用,31 位存储对象的 hash 值(注意这里存储的 hash 值对根据对象地址算出来的 hash 值,不是重写 hashcode 方法里面的返回值),中间有 1 位没有使用,还有 4 位存储对象的 age(分代回收中对象的年龄,超过 15 晋升入老年代),最后三位表示偏向锁标识和锁标识,主要就是用来区分对象的锁状态(未锁定,偏向锁,轻量级锁,重量级锁)
biased object 的对象头 Markword 前 54 位来存储持有该锁的线程 id,这样就没有空间存储 hashcode了,所以 对于没有重写 hashcode 的对象,如果 hashcode 被计算过并存储在对象头中,则该对象作为同步锁时,不会进入偏向锁状态,因为已经没地方存偏向 thread id 了,所以我们在选择同步锁对象时,最好重写该对象的 hashcode 方法,使偏向锁能够生效。
基本类型占用存储空间和指针压缩
基础对象占用存储空间
Java 基础对象在内存中占用的空间如下:
类型
占用空间(byte)
boolean
1
byte
1
short
2
char
2
int
4
float
4
long
8
double
8
另外,引用类型在 32 位系统上每个引用对象占用 4 byte,在 64 位系统上每个引用对象占用 8 byte。
Java 中基础数据类型是在栈上分配还是在堆上分配?
我们继续深究一下,基本数据类占用内存大小是固定的,那具体是在哪分配的呢,是在堆还是栈还是方法区?大家不妨想想看! 要解答这个问题,首先要看这个数据类型在哪里定义的,有以下三种情况。
如果在方法体内定义的,这时候就是在栈上分配的如果是类的成员变量,这时候就是在堆上分配的如果是类的静态成员变量,在方法区上分配的指针压缩
引用类型在 64 位系统上占用 8 个字节,虽然一个并不大,但是耐不住多。
所以为了解决这个问题,JDK 1.6 开始 64 bit JVM 正式支持了 -XX:+UseCompressedOops (需要jdk1.6.0_14) ,这个参数可以压缩指针。
启用 CompressOops 后,会压缩的对象包括:
对象的全局静态变量(即类属性);对象头信息:64 位系统下,原生对象头大小为 16 字节,压缩后为 12 字节;对象的引用类型:64 位系统下,引用类型本身大小为 8 字节,压缩后为 4 字节;对象数组类型:64 位平台下,数组类型本身大小为 24 字节,压缩后 16 字节。当然压缩也不是万能的,针对一些特殊类型的指针 JVM是不会优化的。 比如:
指向非 Heap 的对象指针局部变量、传参、返回值、NULL指针。验证
我们来 new 一个空对象:
class ObjA {
}
理论上一个空对象占用内存大小只有对象头信息,对象头占 12 个字节。那么 ObjA.class 应该占用的存储空间就是 12 字节,考虑到 8 字节的对齐填充,那么会补上 4 字节填充到 8 的 2倍,总共就是 16字节。怎么验证我们的结论呢?JDK 提供了一个工具,JOL 全称为 Java Object Layout,是分析 JVM 中对象布局的工具,该工具大量使用了 Unsafe、JVMTI 来解码布局情况。
首先引入 Maven 依赖:
Copy<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.14</version></dependency>
public class ObjSiZeTest {
public static void main(String[] args) {
ClassLayout classLayout = ClassLayout.parseInstance(new ObjA());
System.out.println(classLayout.toPrintable());
}
}
class ObjA {
}
运行结果:
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
再来测试一下包含int、double、Integer数据的类内存大小是多少?
class ObjA {
private int i;
private double d;
private Integer io;
}
int 类型占 4 个字节 ,double 类型占 8 个字节,Integer 是引用类型,64 位系统占 4 个字节。一共 16 个字节。加上对象头 12 字节,显然不够 8 的倍数,所以还得 4 字节的填充,加起来就是 32 字节。
运行结果:
Instance size: 32 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes total
总结
Java对象内存部分如下图:[注:数组对象头里多了一个数组长度]
本文内容由小茜整理编辑!