Java创建对象的过程
类加载检查
- 虚拟机遇到new指令时,首先去检查常量池中有没有这个类的符号应用,并且检查这个符号引用代表的类是否已被加载、验证、准备、解析、初始化过。如果没有,那么必须先执行相应的类的加载过程。
分配内存
- 为新生对象分配内存,在类加载完成后就可以确定对象所需内存大小,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。分配的方式有”指针碰撞”和”空闲列表”,选择哪种方式由java堆是否规整决定,而java堆是否规整又由所采用的的垃圾收集器是否带有压缩整理功能决定
内存分配的方式
指针碰撞
- 适用于内存规整(没有内存碎片)
- 原理:用过的内存全部整理到一边,没有用过的内存放到另外一边,只要从没有用过的内存方向移动指定对象内存大小的位置即可。
- GC收集器: Serial , ParNew
空闲列表
- 适用于堆内存不规整的情况下
- 原理:虚拟机会维护一个列表,该列表中记录哪些内存是可用的,在分配的时候,找一块足够大的内存块来划分给对象实例,最后更新列表记录
- GC 收集器:CMS
内存分配并发问题
- CAS + 失败重试:CAS 是一种乐观锁的实现方式。所谓乐观锁就是:每次不加锁而是假设没有冲突去完成某项操作,因为如果冲突失败就重试,直到成功为止。虚拟机采用CAS失败重试的方式保证更新操作的原子性
- TLAB(Thread Local Allocation Buffer):为每一个线程先在伊甸区分配一块内存,JVM 在给线程中对象分配内存时,首先在 TLAB 分配,当对象大于TLAB分配中的剩余内存或TLAB的内存用尽时,在采用CAS进行内存分配。
初始化零值
- 内存分配完成后,虚拟机需要将分配到的内存空间都初始化零值(不包括对象头),这一步保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段数据类型所对应的零值。
设置对象头
- 初始化零值完成之后,虚拟机需要对对象进行必要的设置。例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
执行init方法
- 从虚拟机的视角来看,一个新的对象已经产生了,但从java程序的视角来看,对象创建才刚开始,
<init>
方法还没有执行,所有字段都还为零。所以一般来说,执行完 new 指令后会接着执行<init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。