本次主要探讨下jvm中一个对象创建的过程,由于讨论到具体虚拟机创建对象的过程,所以需要将虚拟机目标具体到HotSpot,内存区域具体到堆内存,之前也探讨过jvm中不同区域存储的对象的不同,堆主要用来存储实例对象,让我们一起来看看一个对象的分配、布局和访问的过程。
一、对象的创建
在通过new关键字创建一个对象的过程大致分为:检查存在->类加载->分配内存(空间初始化)->init,下面一步步探讨这个过程。
检查存在:检查new指令的参数,是否在常量池中定位到一个类的符号引用,并且检查这个类的符号引用代表的类是否已被加载、解析和初始化过。
类加载:上一步检查存在如果结果是没有,那么会先执行相应的类加载过程。
分配内存:所需分配的内存大小在类加载完成时便已经确定,在分配内存时有两种情况:①堆中内存是绝对规整的,已使用和未使用的内存各在一边,由一个指针做分界点,这种情况只需要移动指针即可,这种方式称为指针碰撞,②堆内存不规整,已使用和未使用随机分布,虚拟机需要维护一个记录表,用来记录哪些内存块已使用,这种情况下需要在表中找到一块合适的内存分配给对象,并更新记录表信息,这种方式称为空闲列表。选择那种分配方式和堆内存是否规整有关,而堆内存是否规整又和采用的垃圾收集器有关。在分配的过程中,即使是修改一个指针的值,在并发情况下也不是线程安全的,解决方案也有两个:①对分配内存空间的操作进行同步处理(虚拟机采用的是CAS配上失败重试的方式保证这个过程的原子性),②把内存分配的动作按线程划分不同区域,每个线程操作自己的内存(本地线程分配缓冲TLAB),当线程自己的TLAB用完分配新的TLAB时才需要同步处理,可通过 -XX:+UseTLAB参数来设定虚拟机是否使用TLAB。
空间初始化:将分配到的内存空间都初始化为零。这一步保证了对象的实例字段在代码中不赋初始值也可以直接使用。
init:将对象按照代码来进行初始化。
二、对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局主要分为三块:对象头、实例数据、对齐填充。
对象头又包括两部分信息,①存储对象自身运行时的数据(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等),②类型指针,对象的类元数据指针,表示这个对象是哪个类的实例。如果对象是一个数组,在对象头中还有一块内存记录数组的长度。
实例数据:对象存储的有效信息(代码中定义的类型字段内容,包括自身和从父类中继承而来)。
对齐填充:占位符,不必须存在(HotSpot要求对象起始地址需为8字节的整数倍)
三、对象的访问定位
Java程序是通过栈上的reference数据来操作堆中的对象,在虚拟机规范中值规定了reference类型是一个指向对象的引用,没有要求这个引用具体如何定位对象位置,因此对象的访问也是因虚拟机而异。现在主要的方式有两种:使用句柄和直接指针。
①使用句柄,堆中有一块内存用来作为句柄池,reference中存储的是对象的句柄地址,句柄中包含对象实例数据和对象类型数据的具体地址信息。
对象访问两种方式②:直接指针:reference中存储的是对象地址
比较:使用句柄,当对象在被垃圾收集器移动时只会改变句柄的值,不会改变reference的值,使用直接指针。访问速度更快。HotSpot虚拟机使用第二种方式来进行对象访问。
网友评论