对象的创建、对向的结构、对象的访问定位

   在上一篇文章中我们对于jvm的内存模型进行了学习和了解,本篇文章中我们对于对象从创建到如何访问定位进行一个梳理:

一、对象的创建: 

对象创建的流程图:    

对象的创建、对向的结构、对象的访问定位

关于类加载的一部分内容我们暂时先不涉及,我们直接从第四步虚拟机为对象分配内存开始。

(1)给对象分配内存

       其实就是我们在堆内存中划分出来一部分未使用的内存空间,来存储对象的实例。通常情况下我们使用指针碰撞法和空闲列表法来实现:

1.指针碰撞法
         假设Java堆中内存时完整的,已分配的内存和空闲内存分别在不同的一侧,通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离。使用的GC收集器:Serial、ParNew,适用堆内存规整(即没有内存碎片)的情况下。

2.空闲列表法
         事实上,Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。使用的GC收集器:CMS,适用堆内存不规整的情况下。

         我们可以看出不同的垃圾回收器所采用的内存分配方式是不同的,这主要取决于垃圾收集器有没有垃圾规整的策略,也就是说垃圾收集器每次执行完垃圾收集之后有没有对未回收的部分进行统一的规整。

(2)线程安全问题

       不管是指针碰撞还是空闲列表,都会出现线程安全的问题。

       线程同步:会严重影响效率

      本地线程分配缓冲:为每一个线程单独开辟一块存储空间,各个线程在单独的内存区域进行内存的划分,避免相互干扰造成            的线程安全问题。

(3)初始化对象

(4)执行构造方法

 

二、对象的结构:

1、Header(对象头)

       对象头数据模型:

对象的创建、对向的结构、对象的访问定位

       自身运行时数据(Mark Word) :哈希值(通过native方法获取)、GC分代年龄、锁状态标记、线程持有的锁、偏向线程           ID、偏向时间戳

       类型指针:通过类型指针确定该对象是那个类的实例

2、InstanceData

        真正存储的对象的有效信息,

3、Padding

        没有特殊含义,相当于占位符

三、对象的访问定位的两种方式

1、句柄访问

       使用句柄访问,java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

       使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的)时只会改变句柄中的实例数据指针,而reference本身不需要修改。

对象的创建、对向的结构、对象的访问定位

2、直接指针


       使用直接指针访问,java堆中对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

       使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在java中非常频繁,因此这类开销积少成多之后也是一项非常可观的执行成本。

对象的创建、对向的结构、对象的访问定位