注:子类构造器需要调用父类的构造器,直到Object类,所以子类实例在建立时会同时在内存中分配它的父类的一些实例字段。
- 16字节对象的对象头 = 8字节标记字段(hashcode、GC、Lock)+8字节类型指针(指向该对象的类)
- 12字节压缩对象头 = 8字节标记字段(hashcode、GC、Lock)+4字节压缩指针(指向该对象的类,将原本的64指针压缩至32位)
对象内存布局的三点特性:
特性一:内存对齐
一个字段未填满规定大小的内存空间时系统会自动填充缺失的字节数(非有效数据),造成内存浪费。
- 字段与字段之间:
socket跨平台传输结构体时也存在字节对齐现象,系统可能会在某个字段后面添加多个0x00来保证字段一定的字节数要求。
对齐原因:为了让同一个字段在同一缓存行(提高程序执行效率),就像word文档在行末时,会将一整个英文单词放在一行,当该行放不下这个单词时会将其放到下一行,同时该行末尾用空格填充。
- 对象与对象之间:
Java虚拟机中对象的起始地址要为8的倍数,一个对象内存占用没有满8*N时也会被自动填充。
特性二:字段重排
字段重排是为了节省程序占用的内存空间,指令重排是为了提高程序执行的速度。
也就是说对象的字段在内存空间中放置的位置会被移动—为了更好地解决由以上内存对齐造成的浪费,比如一个4字节的int型字段和一个8字节的double型字段之间,本来JVM会插入一个四字节的无效字段,但是此时对象中还存在另一个四字节的int字段,此时JVM会自动将该四字节字段移动到需要填充四个字节的内存空间处填充。
特性三:为了解决虚共享问题引入@Connected注解
虚共享:一个对象的两个不同的volatile字段本来是相互不影响的,但是当他们存在于一行缓存行时,因为操作系统是一缓存行为单位执行操作的,所以对一个volatile变量的读写操作会影响该缓存行上的其他volatile变量。
因此为了解决该问题JVM在volatile修饰的变量中会使用@Connected注解,os底层将该变量单独分配一行缓存行,达到分离两个volatile变量的目的,但是这就造成了内存空间的浪费。
@Connected的应用场景:当一个CPU读取了一行缓存行数据时(64Byte,包含多个变量),另一个CPU2也读取了该缓存行中的部分数据,当CPU1修改了它的缓存行中的部分变量值时,由于缓存一致性的要求,需要通知CPU2重新到内存中读取缓存行最新值,这就会影响CPU2的执行速度。使用该注解可以将一个变量独占一行缓存行,那么CPU1中修改缓存行只会影响该变量值,CPU2缓存行中如果没有读入该数据就不需要重新去内存读数据,不会影响其运行速度。
参考文章:
https://time.geekbang.org/column/article/13081