对象的创建流程
当我们new了一个对象的时候,JVM是如何操作,使这个对象可用的呢?
-
加载检查
虚拟机遇到一条new指令时,首先去检查这个参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用是否已被加载、解析和初始化过。如果没有,那必须先进行相应的类加载过程。 -
分配内存
对象所需内存的大小在加载完成后便可完全确定,这个过程主要是在堆中划分出一块内存来存储对象,内存分配有两种方式:
指针碰撞
:用于经过垃圾回收后,Java堆绝对规整的内存区域,例如复制算法(Serial、ParNew、Parallel Scavenge)和标记整理算法(Serial Old、Parallel Old)。指针碰撞分配内存仅仅是把分隔已用空间和空闲内存的指针向空闲空间那边挪动一段与对象大小相等的距离。
空闲列表
:用于经过垃圾回收后,Java堆不是规整的情况,例如CMS这种基于Mark-Sweep算法的垃圾收集器。空间列表法虚拟机必须维护一个列表,记录哪些内存块是可用的,在分配的时候找到一块足够大的空间分配个对象,并更新表上的记录。分配内存过程中的线程安全:
- CAS : 虚拟机采用CAS配上失败重试的方案保证指针移动过程中的原子性。
- TLAB(本地线程分配缓冲):给每个线程在Java堆中预先分配一小块内存,称为TLAB。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完需要重新分配时,才需要同步锁定。 启用TLAB,通过-XX:+/-UseTLAB来设定。
-
初始化
1.类实例变量初始化:分配完内存空间后需要将对象的实例字段初始化为默认值,如果使用TLAB,这一过程也提前至TLAB分配时进行。
2.类的常量的初始化:类的常量在编译阶段就会为其生成ConstantValue属性,并在类加载阶段的准备
阶段分配内存空间的同时赋值。
3.类的静态变量的初始化:准备阶段后,类静态变量跟实例变量一样,保持默认值,在类构造器<client>
方法执行时,进行初始化赋值操作。
对象的访问定位
句柄
使用句柄访问的话,Java堆会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。好处
是reference存储的是稳定的句柄地址,对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改;坏处
是增加一次指针定位的时间开销,速度比较慢。
直接指针
HotSpot JVM采用的对象访问方式。直接指针访问,reference中存储的是对象地址,类型数据地址存在对象中。好处
速度快,节省了一次指针定位的开销。