平常工作中,我们只new一个对象,却基本不关心这个对象到底占了我们多少空间.
今天就一起看下,对象的空间占用情况
首先,内存中的对象是由以下几部分构成的:
结合上图以下类为例,看下内存空间的占用情况
代码语言:javascript复制public class MyObject {
int i = 123;
long l = 234;
String str = "12345";
}
1. Mark word: 记录线程,锁等对象状态,64位机占用8字节;32位机占用4字节; 当前主机是64位占8字节
2. Klass pointer: 指向类元数据的指针,开启指针压缩时, 占4字节;
3. 数组长度: 如果是数组对象,存储数组长度,如果不是数组,没有该区域;
4. 头部数据补全: 只是在关闭指针压缩,并且是数组对象的情况下才会用到;
5. 实例属性: 存储实例属性; 例如: int变量4字节; long变量8字节; String对象是一个指针占4字节;
6. 对齐补全: JVM中开辟的内存空间必须是8字节的倍数, 如果缺少位数,需要补全为8的倍数; 以上各字段共28字节,需补全4字节
所以MyObject对象共占用8 4 0 16 4=32字节
下面利用openjdk的jol工具包验证下
代码语言:javascript复制public static void main(String[] args) {
MyObject object = new MyObject();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
执行结果:
代码语言:javascript复制MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int MyObject.i 123
16 8 long MyObject.l 234
24 4 java.lang.String MyObject.str (object)
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal 4 bytes external = 4 bytes total
和我们预期是一样,共占用32字节,有4字节的空间损失.
数组结构内存空间
再看下数组结构的内存空间分配
代码语言:javascript复制public static void main(String[] args) {
MyObject[] array = new MyObject[]{new MyObject(),new MyObject()};
System.out.println(ClassLayout.parseInstance(array).toPrintable());
}
对象头部分除MarkWord和klass pointer以外, 还有4字节用来存储数组长度
代码语言:javascript复制[LMyObject; object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) d1 c4 00 f8 (11010001 11000100 00000000 11111000) (-134167343)
12 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2)
16 8 MyObject [LMyObject;.<elements> N/A
Instance size: 24 bytes
Space losses: 0 bytes internal 0 bytes external = 0 bytes total
其他类型的实例属性
再看看其他属性的内存占用情况是如何的.
代码语言:javascript复制public class OtherObject {
public static final float PI = 3.14f;
boolean b = false;
char c = 'a';
short s = 20;
int i = 123;
long l = 234;
float f = 0.1f;
double d = 0.2d;
String str = "12345";
Map map = new HashMap();
Set set = new HashSet();
List list = new ArrayList();
public static void main(String[] args) {
OtherObject object = new OtherObject();
object.map.put("a", "a");
object.set.add("a");
object.list.add("a");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
System.out.println(ClassLayout.parseInstance(object).headerSize());
System.out.println(ClassLayout.parseInstance(object).instanceSize());
}
}
通过执行结果可以发现:
类变量是不用开辟内存空间的
boolean类型占用1个字节,后面需要3字节对齐补全;
short和char类型都需要2字节的空间, 但JVM进行了优化,并不需增加空间对齐补全;
在最后需要4字节对齐补全;
整个对象需要64字节,空间损失了7字节.
代码语言:javascript复制OtherObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int OtherObject.i 123
16 8 long OtherObject.l 234
24 8 double OtherObject.d 0.2
32 4 float OtherObject.f 0.1
36 2 char OtherObject.c a
38 2 short OtherObject.s 20
40 1 boolean OtherObject.b false
41 3 (alignment/padding gap)
44 4 java.lang.String OtherObject.str (object)
48 4 java.util.Map OtherObject.map (object)
52 4 java.util.Set OtherObject.set (object)
56 4 java.util.List OtherObject.list (object)
60 4 (loss due to the next object alignment)
Instance size: 64 bytes
Space losses: 3 bytes internal 4 bytes external = 7 bytes total
指针压缩
指针压缩是指在64位机上,也使用32位表示引用地址.
在上面的例子中都是开启指针压缩的,因为从jdk6之后就是默认开启的.
共涉及到2个JVM参数
-XX: UseCompressedOops
会使用4字节来表示java object的引;
默认开启
-XX: UseCompressedClassesPointers
默认开启
用4字节来表示进程中的class pointer;
UseCompressedClassPointers的开启是依赖于UseCompressedOops的开启.
同样是MyObject对象,在关闭指针压缩时,共需要40字节的空间
Mark down使用8字节
Klass pointer 使用8字节
str指针也占用8字节;
代码语言:javascript复制MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 28 30 60 a2 (00101000 00110000 01100000 10100010) (-1570754520)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 8 long MyObject.l 234
24 4 int MyObject.i 123
28 4 (alignment/padding gap)
32 8 java.lang.String MyObject.str (object)
Instance size: 40 bytes
Space losses: 4 bytes internal 0 bytes external = 4 bytes total
指针压缩的好处
1. 节省内存空间
2. 提升执行效率
头部空间补全
在关闭指针压缩之后,在看数组对象的内存空间,就可以发现产生了数据补全的情况
Mark down使用8字节
Klass pointer 使用8字节
数组长度 使用4字节
头部数据补全 使用4字节
代码语言:javascript复制[LMyObject; object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 3a c2 0c (00000000 00111010 11000010 00001100) (214055424)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2)
20 4 (alignment/padding gap)
24 16 MyObject [LMyObject;.<elements> N/A
Instance size: 40 bytes
Space losses: 4 bytes internal 0 bytes external = 4 bytes total
以上,就是一个对象内存的占用情况.
最后,附上openjdk.jol的maven依赖
代码语言:javascript复制<dependency>
<groupId>org.openjdk.jol</groupId>
<version>0.9</version>
<artifactId>jol-cli</artifactId>
</dependency>