Java对象头的组成
Java对象的对象头由 mark word 和 klass pointer 两部分组成,
MarkWord:哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向的线程ID。
类型指针:指向类元数据指针,方法区的Class模板。
示例数据:实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。
对齐填充:对齐填充并不是必然存在,也没有特别的含义,它仅仅起着占位符的作用。
值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存.64位的JVM比32位的JVM多耗费50%的内存。
我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。
64系统MarkWord图
以64位操作系统为例,对象头存储内容图例。
简单介绍一下各部分的含义
lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。
biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标记 |
age:Java GC标记位对象年龄。
identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
thread:持有偏向锁的线程ID和其他信息.这个线程ID并不是JVM分配的线程ID号和Java Thread中的ID是两个概念。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向线程Monitor的指针。
验证对象头数据
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
无锁验证
package com;
import org.openjdk.jol.info.ClassLayout;
public class ObjectTest {
public static void main(String[] args) {
User p = new User();
int hashCode = p.hashCode();
String hex = Integer.toHexString(hashCode);
System.out.println("HashCode十六进制:"+hex);
print(p);
}
static void print(User p){
System.err.println(ClassLayout.parseInstance(p).toPrintable());
}
}
class User{
private boolean a;
}
输出结果:
输出的第一行内容和锁状态内容对应:
unused:1|age:4|biased_lock:1|lock:2
0 0000 0 01 根据MarkWord图表示User对象正处于无锁状态
第三行中表示的是被指针压缩为32位的klass pointer
第四行则是我们创建的User对象属性信息 1字节的boolean值
第五行则代表了对象的对齐字段 为了凑齐64位的对象,对齐字段占用了3个字节,24bit
至于为什么数据是倒着存储的,请参考“大小端模式”。
GC分代年龄为什么最大为15?
因为Object Header采用4个bit位来保存年龄,4个bit位能表示的最大数就是15。
验证GC垃圾回收
回收前:
public static void main(String[] args) {
User p = new User();
int hashCode = p.hashCode();
String hex = Integer.toHexString(hashCode);
System.out.println("HashCode十六进制:"+hex);
System.gc();
print(p);
}
回收后:
由于手动触发了一次GC所以四个bit位从0000变成了0001.
参照:
https://blog.csdn.net/qq_32099833/article/details/103721326