对象的创建过程
Java 是面向对象的语言,创建对象是日常开发中最为常规的代码。当创建一个对象时,发生以下过程。
对象创建的过程如上
- Jvm 遇到new关键字,根据new的参数,在常量池中定位一个符号引用,如果没有找到符号引用,说明这个类没有被加载,则进行累的加载、解析、初始化。类加载完成后,虚拟机为对象分配内存(一般在堆中),将分配的内存初始化为零值(不包括对象头),最后调用对象的(init)方法。
给对象分配内存方式
- 指针碰撞
在这种方式中,认为堆中的内存都是绝对规整的,用过的内存都放在一侧,空闲的空间在另一侧,中间的指针作为指示器,为对象分配空间时,只是将指针向空闲的一侧移动出对象所需内存大小即可,这种方式叫做指针碰撞(Bump the Pointer) - 空闲列表
如果堆中的内存不是规整的,空闲和被使用的内存交错存在,无法通过简单的指针碰撞方式分配内存,这个时候虚拟机需要维护一张表,用来记录哪些地方的内存可用,在可用内存中,取出对象所需大小,分配给对象,并更些列表。这种方式叫做空闲列表 - 选择内存分配方式
具体选择哪种内存分配方式,使用Jvm堆是否规整决定的,而Jvm堆是否规整,又取决于垃圾收集器是否具有压缩整理的功能,因此在使用Serial、ParNew等带Compact过程的收集器时,采用的是指针碰撞。使用CMS这种基于Mark-Sweep算法的垃圾收集器时,采用的是空闲列表的方式。
Hotspot 使用的G1收集器,具有压缩整理功能,系统采用指针碰撞的方式分配内存。
线程安全问题(处理并发问题)
对象的创建非常频繁,在并发情况下给对象分配存储空间是线程不安全的,解决线程不安全问题有两种方式
- 采用线程同步的处理方式,保证每个创建对象的过程是安全的,这种方式的缺点,大大降低了系统的性能
- 本地线程缓冲(TLAB),即将每个创建对象的过程在空间上分开进行,通过将Jvm堆按照每个线程分配一小块内存,每个线程创建对象分配空间时,只在线程自己的TLAB上进行操作,只有TLAB用完,并分配新的TLAB时,才会进行加锁操作。大大提高系统性能。虚拟机是否使用TLAB,可以通过 -XX:+/-UseTLAB参数来设定。
初始化对象
- 当内存分配完成后,虚拟机将分配到的内存都初始化为零值(不包括对象头,引用类型初始值为null,基本类型为零值,布尔类型初始值为false),如果使用TALB这一过程也可以提前至TALB分配时进行,这个过程就可以解释 为什么对象创建后未赋值前就可以直接使用。
设置对象头信息
- 上面的过程都完成后,虚拟机要为对象设置一些必要信息,例如这个对象是那个对象的实例、如何才能找到对象的元数据信息、对象的哈希码、GC的分代年龄等,这些都被设置在对象头(Object Header)中。此时从虚拟机的角度一个新的对象已经产生了,但从我们开发的角度,才刚刚开始,(init)方法还没有执行,所有的字段都还是零值。
执行构造方法
- class 是从子类向基类依次查找,有关静态初始化的操作,从基类到子类依次进行。在为所创建的对象存储空间清零后,在继承链中最上层的基类开始,依次执行下面两个操作
- 执行其出现在域定义处的初始化动作
- 执行构造方法