Java对象创建过程
1. 类加载检查
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有则进行相应的类加载过程。(我之后会写一篇关于类加载顺序和过程的博客,并在此补充连接地址)
2. 分配内存空间
类加载检查通过之后,JVM将为新生对象在堆中分配内存。对象所需内存的大小在类加载完成后已经完全确定了(关于怎样计算对象所需内存大小我稍后会写一篇博客并补充链接)。为对象分配空间就相当于在Java堆上将一块确定大小的空间划分出来。如果Java堆是连续分配的,一边是存储了对象的,另一边是空闲的,中间放着一个指针作为分界点的指示器,则为对象分配空间只需要将指针想空闲的一边移动对象大小的位置即可。这种方式叫做“指针碰撞”(bump the pointer)。如果Java堆不是连续分配的,虚拟机就需要维护一个列表记录每一个已经分配的对象和没有分配的对空间信息。当需要分配空间时,找出一个足够大的空闲空间分配给新生对象然后更新列表上的记录,这种方式被称为“空闲列表”(Free List)。
选择哪种分配方式是由Java堆是否连续规整决定的,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial, ParNew等带Compact过程的收集器时,系统采用的是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用的是空闲列表。
3. 设置对象基本信息
对象分配到空间之后,JVM会将分配到的内存空间都初始化为零值(不包括对象头)。之所以不对对象头做初始化零值设置,是因为虚拟机要对对象进行必要的基本信息设置。如该对象是哪个类的实例、如果找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中,被叫做“Mark Word”。(对象的内存布局我接下来会写两篇博客详细讲解,并在此补充连接地址。)
4. 程序员意愿的初始化与调用构造函数
以上工作都做完之后,对JVM来说,一个对象已经创建完毕。但是从Java程序的视角来说,对象创建才刚刚开始:首先程序会按照程序员的定义来初始化对象(如定义了成员变量 private int age = 10,此时会设置age的值为10),这样一个真正可用对象才算完全产生出来。然后程序会调用构造方法。到此为止,一个对象才算完全完成了创建和初始化的工作,可以使用了。
例子
public class InitDemo {
private int age = 10;
public InitDemo() {
System.out.println("before: " + age);
age = 20;
System.out.println("after: " + age);
}
public static void main(String[] args) {
InitDemo demo = new InitDemo();
/**输出结果:
before: 10
after: 20
*/
}
}
}
声明:文章内容是对《深入理解Java虚拟机——JVM高级特性与最佳实现》第二版相关章节学习的总结。