1. 执行类加载过程

虚拟机遇到一条new指令时,首先检查该对象对应的类是否已被加载、解析和初始化过,如果没有,则先执行相应的类加载过程。

2. 为对象分配内存

类加载检查通过后,虚拟机就会为新生对象分配内存(对象所需的内存大小在类加载完成后便可完全确定),为对象分配内存的任务相当于从Java堆中划分出一块确定大小的内存,根据Java堆中内存是否是规整的(是否规整由采用的垃圾收集器是否带有压缩整理的功能决定),有两种内存分配方式:

  • 1)指针碰撞方式:所有用过的内存放在一边,空闲的放在另一边,中间有一个指针作为分界点,分配内存就是将指针移动一段与对象内存大小相等的距离。
  • 2)空闲列表方式:由于已使用的内存和空闲内存是交错分配的,所以需要知道内存可分配,为此,虚拟机维护一个空闲列表,用来记录哪些内存块是可用的,在分配的时候,直接从列表中找到一个足够大的空间划分给对象,然后更新列表记录。

还需要考虑并发情况下内存分配时线程安全问题,可能正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存。JVM采用给每个线程在堆中首先分配一块线程私有的分配缓冲TLAB,哪个线程需要分配内存,就在哪个线程的TLAB上分配。

3. 内存空间初始化

内存分配后,JVM将分配到的内存空间都初始化为零值(不包括对象头),所以我们在定义Java类的实例字段中可以不赋初值就直接使用,程序能访问到这些数据类型对应的零值。

4. 设置对象头

设置对象运行时所需要的必要信息,如对象是哪个类的实例,对象的哈希码、GC分代年龄等。

对象在内存中的布局分为三部分:

  • 对象头:包括两部分信息,第一部分存储对象自身运行时数据,如哈希码、GC分代年龄、线程持有的锁等,称为“Mark Word”。在32位和64位虚拟机下的长度分别为32bit和64bit。另一部分是类型指针,用来指向所属类,虚拟机通过这个指针来确定这个对象是哪个类的实例;如果是对象是一个数组的话,还有一块用于记录数组长度的数据。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  • 实例数据:是对象真正存储的有效信息,也是我们在程序中所定义的各种类型的字段内容。这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在 Java 源码中定义顺序的影响。
  • 对齐填充:因为HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,也就是对象的大小必须是8字节的整数倍,对象头的大小已经是8字节的整数倍,因此,当实例数据没有对齐时,会通过对齐填充来使整个对象大小为8字节的整数倍。

参考:https://www.cnblogs.com/zhengbin/p/6490953.html

5. 实例对象初始化

上面的工作完成后,从虚拟机的角度看,一个对象已经产生了,但从Java程序的角度,对象的创建才刚刚开始,我们在对象中定义的成员变量都还没初始化,还是零值。对于实例对象执行new指令后还会接着执行<init>方法,初始化其中的实例变量以及构造块等。

这里注意<init><clinit>方法的区别:

这两个方法都是Java虚拟机生成的,Java在编译之后会在字节码文件中生成<init><clinit>。都是用来放置初始化相关的代码的。

  • <init>方法:称之为实例构造器,Java编译后会将初始化块、变量初始化、调用父类的构造函数和自身的构造函数等操作都统一放到该方法中去执行。操作顺序为:
  1. 父类变量初始化 2. 父类初始化块 3. 父类构造函数 4. 子类变量初始化 5. 子类初始化块 6. 子类构造函数
  • <clinit>方法:称之为类构造器,将静态初始化块、静态变量初始化等都统一收敛到该方法中,收敛顺序为:
  1. 父类静态变量初始化 2. 父类静态初始化块 3. 子类静态变量初始化 4. 子类静态初始化块

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class X {

static Log log = LogFactory.getLog(); // <clinit>

private int x = 1; // <init>

X(){
// <init>
}

static {
// <clinit>
}

}

<clinit>方法是在类加载的时候执行的,而<init>是在对象实例化时执行的(即执行new指令的时候),所以<clinit>会比<init>先执行。所以最终整个初始化的顺序为:

  1. 父类静态变量初始化
  2. 父类静态初始化块
  3. 子类静态变量初始化
  4. 子类静态初始化块
  5. 父类变量初始化
  6. 父类初始化块
  7. 父类构造函数
  8. 子类变量初始化
  9. 子类初始化块
  10. 子类构造函数

参考:

  1. 《深入理解Java虚拟机》
  2. https://stackoverflow.com/questions/8517121/java-what-is-the-difference-between-init-and-clinit