这里指普通 Java 对象,而非数组 和 Class对象等。

1.创建对象的过程

new ---> 到常量池中检查是否存在一个类的符号引用 ---> 如果有,检查这个符号引用代表的类是否已被加载、解析、初始化 ---> 没有,则执行类加载过程。

2.分配对象

类加载完毕后,为新生对象分配内存。
对象所需内存大小在类加载完成后便完全确定。分配空间。即,从JVM堆中划出一块确定大小的内存空间。

3.分配方法

有两种分配方法:

  1. 指针碰撞:
    使用这种方式的前提是,内存是规整的。
    左边是空闲的空间,右边是已使用的内存空间,用一块,就往左边移一块,就像秤砣在称中间移动一样。
  2. 空闲列表
    内存不规整,此时不能使用指针碰撞。JVM 就需要维护一个列表,记录哪些空间可用并给对象。

堆内存是否规整决定了使用哪种分配方法。
而堆是否会规整则由GC是否带有压缩整理功能决定。

4.避免并发操作同一片内存的问题

并发时,A线程 拿着指针在为对象分配 0x111111 这片空间,分配动作执行到一半,B线程 也进来了拿着同一个指针在同一个地址上分配空间。这就出现了问题。

解决方案

  1. CAS 配置失败重试的方式保证更新操作的原子性。
  2. 为每个线程开一小块内存空间,称为本地内存分配缓冲(TLAB)。线程在自己的TLAB是分配。

5.初始化

当分配完成后,JVM将分配到的内存空间开始进行初始化为 0 值(不包括对象头),0值就是数学中的0。如果是TLAB方式,提前到TLAB中分配时进行。
这就是使用对象时,程序访问某些字段的数据类型默认有0的原因。就可以不用赋值也可以使用。

6.必要设置

接下来JVM对对象进行必要设置。
设置如:对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、GC分代年龄等。
这些信息存放在对象头中。
对JVM来说,对象已分配完成,一个新对象就此产生。
但从 java 程序的角度来说对象创建才刚开始。调用 init 方法前,所有字段都是默认的0。执行init方法,对象进行初始化,这样一个真正可用的对象才算完全产生。