对象的创建过程和访问定位方式
@[toc]
---
## 对象的创建过程(new 一个对象在堆中的历程)
1)**类加载检查**:当 Java 虚拟机遇到一条字节码 new 指令时,它会首先检查根据 class 文件中的常量池表(Constant Pool Table)能否找到这个类对应的符号引用;然后去**方法区中的运行时常量池**中查找**该符号引用所指向的类是否已完成了类加载过程(加载、验证、准备、解析、初始化)**
- 如果没有,那就先执行相应的类加载过程
- 如果有,那么进入下一步,为新生对象分配内存
> **常量池表 (Constant Pool Table)**:
>
> 用于存放**编译期**生成的各种字面量(字面量相当于 Java 语言层面常量的概念,如文本字符串,声明为 final 的常量值等)与符号引用。有一些文章会把 class 常量池表称为**静态常量池**。
>
> 都是常量池,常量池表和方法区中的运行时常量池有啥关系吗?运行时常量池是干嘛的呢?
>
> **运行时常量池可以在运行期间将 class 常量池表中的符号引用解析为直接引用**。简单来说,class 常量池表就相当于一堆索引,运行时常量池根据这些索引来查找对应方法或字段所属的类型信息和名称及描述符信息
2)**分配内存**:就是在堆中给划分一块内存空间分配给这个新生对象用。具体的分配方式根据【堆内存是否规整】有两种方式:
- 堆内存规整的话采用的分配方式就是【<u>指针碰撞</u>】:所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,分配内存就是把这个指针向空闲空间方向挪动一段与对象大小相等的距离
- 堆内存不规整的话采用的分配方式就是【<u>空闲列表</u>】:所谓内存不规整就是已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,JVM 就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的连续空间划分给这个对象,并更新列表上的记录,这就是空闲列表的方式
3)**初始化零值**:<u>对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充</u>,对齐填充仅仅起占位作用,没啥特殊意义,<u>初始化零值这个操作就是初始化实例数据这个部分</u>,比如 boolean 字段初始化为 false 之类的
4)**设置对象头**:这个步骤就是设置对象头中的一些信息,比如偏向锁之类的
5)**执行 init 方法**(就是构造函数):最后就是执行构造函数,构造函数即 Class 文件中的 `<init>()` 方法,一般来说,new 指令之后会接着执行 `<init>()` 方法,按照构造函数的意图对这个对象进行初始化,这样一个真正可用的对象才算完全地被构造出来了
## 对象的访问定位方式(如何在堆中找到某个对象的具体位置)
对象的访问定位方式是由虚拟机 GC 的具体实现来决定的,**保守式 GC** 使用的对象访问定位方式是使用**句柄访问**,**准确式 GC** 使用的对象访问定位方式是**直接指针访问**:
- **所谓保守式 GC 就是虚拟机无法识别指针和非指针**(无法识别引用数据类型和基本数据类型),这会导致两个问题,一个就是一些已经死掉的对象无法被回收,占用内存;第二个就是对象无法移动,为了解决这个问题,在堆中引入了【**句柄池**】,**所有引用先指到一个句柄池里**,再从句柄池找到实际对象。这样,要移动对象的话,只要修改句柄池里的内容即可,虚拟机栈中存储的就是对象的句柄地址。这就是使用句柄访问,显然它多了一次间接查找的开销

- **所谓准确式 GC 就是虚拟机准确的知道内存中某个位置的数据具体是什么类型**,具体的实现方式就是使用一个**映射表**(在 HotSpot 中把这种映射表称之为【**OopMap**】)记录下类型信息,虚拟机栈中存储的直接就是对象地址,这样就不需要多一次间接访问的开销了,这就是直接指针访问
现在主流的 Hotspot 虚拟机,基本都是准确式 GC
