对象内存布局
对象里的三个区:
-
对象头(Header):Java对象头占8byte。如果是数组则占12byte。因为JVM里数组size需要使用4byte存储。
标记字段MarkWord:
用于存储对象自身的运行时数据,它是synchronized实现轻量级锁和偏向锁的关键。
默认存储:对象HashCode、GC分代年龄、锁状态等等信息。
为了节省空间,也会随着锁标志位的变化,存储数据发生变化。
标记字段的结构:
类型指针KlassPoint:
是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
开启指针压缩存储空间4byte,不开启8byte。
JDK1.6+默认开启
数组长度 如果对象是数组,则记录数组长度,占4个byte,如果对象不是数组则不存在。
对齐填 保证数组的大小永远是8byte的整数倍。 -
实例数据(Instance Data):生成对象的时候,对象的非静态成员变量也会存入堆空间
-
对齐填充(Padding):JVM内对象都采用8byte对齐,不够8byte的会自动补齐。
案例1:
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version>
</dependency>
Object o = new Object();System.out.println("new Object:" +ClassLayout.parseInstance(o).toPrintable());
注:首先对象头是包含MarkWord和类型指针这两部分信息的;
开启指针压缩的情况下,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节;
新建Object对象,会在内存占用16个字节,其中Header占12个(MarkWord占8个+KlassPoint占4个),没有实例数据,补充对齐4个。
结论:对象大小 = 对象头12 + 实例数据0 + 对齐填充4 = 16 bytes
案例2:
public class TT {public static void main(String[] args) {Hero a = new Hero();System.out.println("new A:" +ClassLayout.parseInstance(a).toPrintable());Hero b = new Hero(1, true, "test");System.out.println("赋值后:" +ClassLayout.parseInstance(b).toPrintable());}static class Hero {int i;boolean flag;String str;public Hero() {}public Hero(int i, boolean flag, String str) {this.i = i;this.flag = flag;this.str = str;}}
}
对象的大小 = 12对象头 + 4*3的实例数据 + 0的填充 = 24bytes
对象头存储信息分析
1 如果是空对象
Object obj = new Object();
2 带锁的对象
可以看一下这篇文章关于头的顺序
- 偏向锁
// java 默认5s以上才会开启偏向锁
Thread.sleep(5001);
Object lock = new Object();
printObj(lock, 1);
synchronized (lock) {printObj(lock, 2);
}
- 轻量级锁
Thread.sleep(5001);Object lock = new Object();printObj(lock, 1);synchronized (lock) {printObj(lock, 2);}Thread.sleep(1000);new Thread(() -> {synchronized (lock) {System.out.println("get lock one");printObj(lock, 3);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}).start();
这里我们有Main线程和新开的一个线程来竞争资源,所以就会升级为一个轻量级锁。
- 重量级锁
for (int i = 0; i < 10; i ++) {new Thread(() -> {synchronized (lock) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("get lock three");}}).start();printObj(lock, 5);
对象的访问
有两种方式:
-
句柄:稳定,对象被移动只要修改句柄中的地址
-
直接指针:访问速度快,节省了一次指针定位的开销
对象类型数据存储的是对象的元信息,比如有哪些字段,那些方法