JUC高级八-Java对象内存布局和对象头
1. 对象的内存布局
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)
、实例数据(Instance Data)
和对齐填充(Padding)
(保证8个字节的倍数) 。
数组对象的对象头比常规对象多一个length,用于记录数组长度
1.1 对象头
1.1.1 对象标记Mark Word
1.1.1.1 32位(看一下即可,不用学了,以64位为准)
1.1.1.2 64位(重要)
- 默认存储对象的
HashCode
、分代年龄
和锁标志位
等信息。 - 这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。
- 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
1.1.1.2.1 源码分析
oop.hpp
markOop.hpp
- hash: 保存对象的哈希码
- age: 保存对象的分代年龄
- 从下图
age:4
可以看出分代年龄用4位表示,那么最大就是二进制1111
即十进制15,所以新生代对象晋升为老年代对象需要分代年龄达到15次- biased_lock: 偏向锁标识位
- lock: 锁状态标识位
- JavaThread* :保存持有偏向锁的线程ID
- epoch: 保存偏向时间戳
1.1.1.2.2 markword(64位)分布图
对象布局、GC回收和后面的锁升级就是对象标记MarkWord里面标志位的变化
1.1.2 类元信息(又叫类型指针)
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
对象头多大?
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。
1.2 实例数据
存放类的属性(Field)数据信息,包括父类的属性信息
1.3 对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。
Hotspot术语表官网
底层源码理论证明
_mark字段是mark word,_metadata是类指针klass pointer,
对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,
2. 聊聊Object obj = new Object()
2.1 JOL工具–分析对象在JVM的大小和分布
JOL官网
pom依赖
<!--
官网:http://openjdk.java.net/projects/code-tools/jol/
定位:分析对象在JVM的大小和分布
-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
2.1.1 VM的细节详细情况示例
package site.zhourui.juc.objectHead;
import org.openjdk.jol.vm.VM;
public class JOLDemo {
public static void main(String[] args) {
//VM的细节详细情况
System.out.println(VM.current().details());
//对象对齐:所有的对象分配的字节都是8的整数倍。
System.out.println(VM.current().objectAlignment());
}
}
执行结果:
打印出VM的细节详细情况及对象对齐
2.1.2 查看Object内存布局示例
package site.zhourui.juc.objectHead;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o));
}
}
执行结果
之前说类型指针是8个字节现在只有4个字节?后面解释
2.1.3 只有对象头没有其他任何示例数据的对象内存布局
package site.zhourui.juc.objectHead;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
// System.out.println(ClassLayout.parseInstance(o).toPrintable());
Customer customer = new Customer();
System.out.println(ClassLayout.parseInstance(customer).toPrintable());
}
}
class Customer{//只有一个对象头的实例对象,16字节(忽路压缩指针的影响)+4字节+1字=-21字节----》对其填充,24字节
}
执行结果:
我们发现与new一个Object的对象内存布局一样的
2.1.4 有实例数据对象内存布局
package site.zhourui.juc.objectHead;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class JOLDemo {
public static void main(String[] args) {
Object o = new Object();
// System.out.println(ClassLayout.parseInstance(o).toPrintable());
Customer customer = new Customer();
System.out.println(ClassLayout.parseInstance(customer).toPrintable());
}
}
class Customer{//只有一个对象头的实例对象,16字节(忽路压缩指针的影响)+4字节+1字=-21字节----》对其填充,24字节
int id;
boolean flag;
}
2.1.5 GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15
设置虚拟机分代年龄参数为16-XX:MaxTenuringThreshold=16
执行结果:
说虚拟机分代年龄只能是0到15
2.1.6 尾巴参数之压缩指针相关说明
2.1.6.1 查看jvm启动默认参数
-XX:+PrintCommandLineFlags -version
执行结果:
发现jvm启动的时候是默认帮我们开启了压缩指针UseCompressedClassPointers
就会导致我们的类型指针本来是8字节被压缩为4个字节
2.1.6.2 关闭压缩指针再次查看对象内存布局
-XX:-UseCompressedClassPointers
执行结果:
发现没有对齐填充了,我们的类型指针是8字节