对象内存布局
对象里的三个区:
-
对象头(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:
xml
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
java
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:
java
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 如果是空对象
java
Object obj = new Object();
2 带锁的对象
可以看一下这篇文章关于头的顺序
- 偏向锁
java
// java 默认5s以上才会开启偏向锁
Thread.sleep(5001);
Object lock = new Object();
printObj(lock, 1);
synchronized (lock) {
printObj(lock, 2);
}
- 轻量级锁
java
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线程和新开的一个线程来竞争资源,所以就会升级为一个轻量级锁。
- 重量级锁
java
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);
对象的访问
有两种方式:
-
句柄:稳定,对象被移动只要修改句柄中的地址
-
直接指针:访问速度快,节省了一次指针定位的开销
对象类型数据存储的是对象的元信息,比如有哪些字段,那些方法