### 1、对象的实例化

#### 1.1、创建对象的方式
- 通过new的方式
- 例如:A a = new A();
- 使用类的静态方法创建对象,实质上也是通过new的方式;
- 使用单例模式、工厂模式创建类的对象,实质上也是通过new的方式;
- Class的newInstance()。反射的方式,但是只能调用无参构造器,并且权限必须是public;
- Constructor的newInstance(Xxxx)方法。反射的方式,可以调用无参、有参构造器,权限没有限制;
- 使用clone()。不调用任何构造器,但是当前类必须实现Cloneable接口,并且实现clone()方法;
- 使用反序列化。从文件、网络中获取对象的二进制流;
- 第三方库。例如Objebesis。
#### 1.2、对象创建的步骤

- **判断对象对应的类是否被加载、连接、初始化**;
虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。( 即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class类对象。
- **为对象分配内存**;
首先计算对象占用空间大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
- 如果内存是规整的,那么虚拟机将采用的是指针碰撞法(Bump The Pointer)来为对象分配内存。意思是所有用过的内存在一边, 空闲的内存在另外一边,中间放着一一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是Serial、ParNew这种基于压缩算法的,虚拟机采用这种分配方式。一般使用带有compact (整理)过程的收集器时,使用指针碰撞。
- 如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为“空闲列表(Free List)”。
- **处理并发问题**;
- 使用CAS失败重试、区域加锁保证更新的原子性。
- 每个线程预先分配一块TLAB。
- **初始化分配到的空间**;
所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用。
- **设置对象的对象头**;
将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现.
- **执行init方法进行初始化**;
在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。因此,一般来说(由字节码中是否跟随有invokespecial指令所决定), new指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。
### 2、对象的内存布局

在运行时数据区中,对象的内存布局如下如:

#### 2.1、对象头
对象头包含两部分:运行时元数据(哈希值hashcode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳),类型指针(指向元数据InstanceKlass,确定该对象所属的类型)。如果创建的是数组,还需要记录数组的长度。
#### 2.2、实例数据
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承的和自身拥有的字段)。实例数据的分配规则是相同的字段总是被分配在一起;父类中出现的变量总会出现在子类之前;如果compactFields参数为true(默认为true),子类的窄变量可能插入到父类的空隙。
### 3、对象的访问定位
JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?首先通过栈上引用reference,指向堆区的InstanceOopDesc,之后堆区的InstanceOopDesc通过元数据指针指向方法区的InstanceKlass。具体过程如图:

对象的访问方式主要有两种:
- 句柄访问;

- 直接指针(hotspot采用的方式)


循序渐进Java虚拟机-对象的实例化、内存布局与访问定位