1. 对象创建过程
创建一个对象,首先是把class文件加载到内存,具体包括三步:具体请看class文件加载过程https://blog.csdn.net/wqc8994/article/details/107928769。然后需要给对象申请内存,接下来成员变量赋默认值,最后调用构造方法。调用构造方法时先调用父类构造方法,然后成员变量赋初始值,接着在执行构造方法语句。
2. 对象的内存布局
对象的内存布局分为两种,一种为普通对象,一种为数组对象。
普通对象 | 数组对象 |
对象头 | 对象头 |
ClassPointer指针 | ClassPointer指针 |
实例数据 | 数组长度 |
Padding对齐 | 数组数据 |
Padding对齐 |
对象头:Hotspot被称为markword,长度为8个字节。
ClassPointer指针:指向对应的class对象,开启-XX:+UseCompressedClassPointers为4个字节,不开启为8个字节。
数组长度:4个字节
实例数据/数组数据:1. 基本类型 基本类型数据长度
2. 引用类型 -XX:+UseCompressedOops开启为4字节,不开启为8字节
Padding对齐:是整个对象长度为8的倍数
3. markword具体内容
锁状态 | 25bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 是否偏向锁 | 锁标志位 | ||
无锁态 | 对象的hashcode | 分代年龄 | 0 | 01 | |
轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | |||
GC标记 | 空 | 11 | |||
偏向锁 | 线程ID | Epoch | 分代年龄 | 1 | 01 |
4. 对象定位
对象定位有2种方式:句柄池和直接指针。
句柄池:java堆中划分一块内存作为句柄池,句柄包含了对象实例数据的指针和对象类型数据的指针,Java栈本地变量表的ref存储的是句柄地址。
直接指针:堆对象包含对象类型数据的指针和对象实例数据,Java栈本地表量表的ref存储的是对象地址。
HotSpot是使用了直接指针定位对象。
5. 对象分配
对象创建时首先尝试在栈内存分配,如果栈内存分配不下,特别大的对象就直接分配在堆内存老年代,如果不大,首先本地线程分配,本地线程分配不下分配到eden区。当GC年龄到时再转移到老年代。流程如下图:
TLAB:Thread Local Allocation Buffer,线程本地分配缓存区。每个线程在eden区占用1%空间为该线程独有。不与其他线程产生争用。