2哥:3妹,昨天我们已经学习了Java的内存模型,那你知道一个对象的创建过程是怎样的吗?
3妹:这个我知道,我可是个勤奋好学的好学生,昨天你给我讲过之后我又在网上查了下。
2哥:是吗, 那你给我介绍下。
对象的创建流程图:
这里借用网上的一张流程图:
1.类加载检查
首先代码中new关键字在编译后,会生成一条字节码new指令,当虚拟机遇到一条字节码new指令时,会根据类名去方法区运行时常量池找类的符号引用,检查符号引用代表的类是否已加载,解析和初始化过。如果没有就执行相应的类加载过程。
2.分配内存
虚拟机从Java堆中分配一块大小确定的内存(因为类加载时,创建一个此类的实例对象的所需的内存大小就确定了),并且初始化为零值。内存分配的方式有指针碰撞和空闲列表两种,取决于虚拟机采用的垃圾回收期是否带有空间压缩整理的功能。
指针碰撞
如果垃圾收集器是Serial,ParNew等带有空间压缩整理的功能时,Java堆是规整的,此时通过移动内存分界点的指针,就可以分配空闲内存。
空闲列表
如果垃圾收集器是CMS这种基于清除算法的收集器时,Java堆中的空闲内存和已使用内存是相互交错的,虚拟机会维护一个列表,记录哪些可用,哪些不可用,分配时从表中找到一块足够大的空闲内存分配给实例对象,并且更新表。
3.对象初始化(虚拟机层面)
虚拟机会对对象进行必要的设置,将对象的一些信息存储在Obeject header 中。
4.对象初始化(Java程序层面)
在构造一个类的实例对象时,遵循的原则是先静后动,先父后子,先变量,后代码块,构造器。在Java程序层面会依次进行以下操作:
- 初始化父类的静态变量(如果是首次使用此类)
- 初始化子类的静态变量(如果是首次使用此类)
- 执行父类的静态代码块(如果是首次使用此类)
- 执行子类的静态代码块(如果是首次使用此类)
- 初始化父类的实例变量
- 初始化子类的实例变量
- 执行父类的普通代码块
- 执行子类的普通代码块
- 执行父类的构造器
- 执行子类的构造器
PS:如何解决内存分配时的多线程并发竞争问题?
内存分配不是一个线程安全的操作,在多个线程进行内存分配是,可能会存在数据不同步的问题。所以有两种方法解决:
-
添加CAS锁
对内存分配的操作进行同步处理,添加CAS锁,配上失败重试的方式来保证原子性。(默认使用这种方式)。 -
预先给各线程分配TLAB
预先在Java堆中给各个线程分配一块TLAB(本地线程缓冲区)内存,每个线程先在各自的缓冲区中分配内存,使用完了再通过第一种添加CAS锁的方式来分配内存。(是否启动取决于-XX:+/-UseTLAB参数)。