创建对象

当遇到一个 new 操作符后,HotSpot 创建一个指定的对象,过程大致如下:

  1. 获取类型信息
    检查指定的类是否能在运行时常量池中找到相应的符号引用,符号引用代表的类是否完成类加载。
  2. 分配内存
    这部分的操作与操作系统为进程进程分配内存相似,可以通过指针碰撞或者空闲链表来完成。
    值得注意的是分配内存同样具有并发问题(不过由 jvm 解决了),采用将分配内存的操作同步处理,或者给每个线程线程划定一个缓冲区,每个线程在相应的缓冲区中分配内存。
  3. 内存置 0
    分配完内存之后 jvm 虚拟机需要将对象对应的内存区域置为 0 (不包括对象头),这个操作保证了在未赋值的情况下为相应的 0 值。比如数字型的基础数据类型为 0,而对象引用为 null。避免了对象引用指向非法内存地址的问题。
  4. 设置对象头
    对象头中的信息包括对象的类型,类的元数据位置,hash 值(推迟计算),GC 分代年龄等。
  5. 执行初始化块和构造函数

对象内存布局

一个对象有 3 部分的信息:对象头,实例数据,对齐填充。

对象头

Mark Word

用于存储对象自身的运行时数据,如哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分数据的长度在 32 位和 64 位的虚拟机(未开启压缩指针)中分别为 32 个比特和 64 个比特。

32 位虚拟机的 Mark Word 如下:

类型指针

指向对象的类型的元数据,用来判断这个对象的实际类型。另外,当这个对象表示一个数组,对象头中还需要记录数组的长度。

实例数据

存放类的实例变量(包括父类中定义的实例变量)。在分配内存时,为了尽量地将内存对齐,长度相同的值存放在一起(比传说 double 和 long),在满足这个条件下,父类中的变量在子类变量之前出现。

对齐数据

将对象占用的内存填充至 8 字节的倍数,以满足规范。

定位对象

对象引用在 jvm 中以 reference 类型数据表示,而具体的访问方式有虚拟机决定。

通过句柄访问

优势是在对象移动之后,只需要修改句柄的指向的地址即可。

直接访问

优势是访问更快。