java对象的内存布局
1.java的内存布局
对象在内存中的存储可以分为三个区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。
对象头
- Mark Word:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。在32位系统占4字节,在64位系统中占8字节;
-
32bit虚拟机中的Mark Word存储结构
-
64bit虚拟机中的Mark Word存储结构
- 类型指针(Class Pointer)
- 用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节
- 数组长度
- 如果对象是个数组的话,对象头中就必须要有一块用于记录数组长度的数据,不然虚拟机无法确定数组的大小。32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项(开启指针压缩),该区域长度也将由64位压缩至32位。32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。
实际数据
对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节(64位系统中是8个字节)。
Primitive Type | Memory Required(bytes) |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
ref | 4 或8 |
对于reference类型来说,在32位系统上占用4bytes, 在64位系统上占用8bytes。
对象填充
Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。例如,一个包含两个属性的对象:int和byte,如果在32位系统中则占用需要4(mark word)+4(类型指针)+4(int)+1(byte)=13个字节,这时就需要加上大小为3字节的padding进行8字节对齐,最终占用大小为16个字节;如果在64位系统中未开启压缩指针(后续会介绍)的话占用8+8+4+1=21,加上大小为3字节的padding进行8字节对齐,最终占用大小为24个字节;如果在64位系统开启了压缩指针的话占用8+4+4+1=17,加上大小为7字节的padding进行8字节对齐,最终占用大小也为24个字节,但是这只是巧合,正好与未开启压缩指针相等。
2.验证对象的内存布局
添加maven依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
写一个测试程序
package com.renlijia.multithread;
import org.openjdk.jol.info.ClassLayout;
/**
* @Author: ding
* @Date: 2020-06-15 23:12
*/
public class Mem_Obj_内存布局 {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
执行后结果:
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 09 80 0d (10010000 00001001 10000000 00001101) (226494864)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
对于锁的具体解释参照下文中的synchronized锁的介绍
3.压缩指针理解
安装jdk后在控制台执行:java -XX:+PrintCommandLineFlags -version可以打印出结果:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
这里面有两个参数:UseCompressedClassPointers和UseCompressedOops是跟压缩指针相关,UseCompressedClassPointers开启后,对象布局里的类型指针会进行压缩,从8字节压缩到4个字节,这个参数只对类型指针进行压缩;UseCompressedOops是对普通的对象进行压缩,例如Object obj = new Object();会在内存中创建一个对象,并将指针指向它,这里面的指针是普通对象被引用的指针,开启后对此指针进行压缩,从8字节编程4个字节
压缩指针失效
- 在32位系统中是没有压缩指针的,不存在失效与否,指针都是4个字节;
- 64位系统开启了压缩指针后,指针占用的存储从8字节变成4字节,但是当系统内存大于32G后,压缩指针失效,因为内存布局是8字节对齐,所以使用指针时,32G的数据实际只有4G的指针指向,因为每个对象占用的都是8的倍数的字节数(8字节对齐),所以使用4G就可以表示出32G的数据量,但是超过32G就无法表示,压缩指针失效,指针都占用8个字节,这就是为什么当内存从<32G变成大于32G后,应用占用的内存增大的原因;
- 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
开启和取消压缩指针
-XX:+UseCompressedOops 开启对象的压缩指针
-XX:-UseCompressedOops 取消对象的压缩指针
-XX:+UseCompressedClassPointers 开启类型指针的压缩
-XX:-UseCompressedClassPointers 取消类型指针的压缩
在小于32G的内存系统中压缩指针是默认开启的,当取消压缩指针,重新执行 Mem_Obj_内存布局 程序结果:
-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
java.lang.Object 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 2c c0 27 (00000000 00101100 11000000 00100111) (666905600)
12 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 09 63 03 (10010000 00001001 01100011 00000011) (56822160)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 00 2c c0 27 (00000000 00101100 11000000 00100111) (666905600)
12 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
这个时候类型指针使用8个字节
参考文章:
https://www.jianshu.com/p/91e398d5d17c
https://zhuanlan.zhihu.com/p/50984945
https://blog.csdn.net/hongzb_2296/article/details/86505898
https://blog.csdn.net/goodmentc/article/details/45880351
https://blog.csdn.net/tongdanping/article/details/79647337