前言
在面试过程中,面试官在问Java内存区域相关问题的时候常常会问到Java创建对象的过程是什么(5个步骤)?此文为博主阅读《深入理解Java虚拟机》后的整理总结,如果喜欢此文的话希望小伙伴们多多支持。
下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
下图为Java对象创建过程5个步骤的脑图剖析:
Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。(具体的类加载过程会在下一篇博文中详细介绍)
Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
Step2.1:内存分配的两种方式:(需要掌握)
- 指针碰撞:在内存规整的情况下,把所有用过的内存放在一边,没有用过的内存放在一边,中间有一个分界指针。在分配对象内存的时候,只要将内存指针向着没有用过的内存方向移动与对象大小相等的距离,那么就完成了内存分配,这种分配方式叫做指针碰撞。
- 空闲列表:在内存不规整的情况下,虚拟机会维护一个列表,用来记录哪些内存块可用。在分配对象内存的时候,会从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,就完成内存分配,这种分配方式叫做空闲列表。
下图两种内存分配方式的脑图:
Step2.2:内存分配并发问题:(需要掌握)
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
- TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段(实例字段是指成员变量)在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
举例说明:
public class Main {
public int a;
public boolean b;
public Test test;
public static void main(String[] args) {
Main object = new Main();
//输出object对象中成员变量a、b和test
System.out.println(object.a);
System.out.println(object.b);
System.out.println(object.test);
}
}
输出结果如下:
没错正是因为对Main这个类进行了初始化零值,所以可以在不给成员变量初始化也可以输出默认值。
Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init>
方法(指执行类中的构造方法)还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行<init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
最后
都看到最后了,求求大家点个赞再走吧!你的支持是博主创作的最大动力。