05-JVM对象探秘

1、对象的内存布局

        以Hotspot虚拟机为例,对象在内存中的结构能够分为三部分:对象头(header)、实例数据(instance data)、对齐填充(padding)。

1.1.对象头

        对象头的结构大致类似,但不一样JVM的具体实现使得它们略有差异。通常来讲,对象头都包含了标记字、类型指针两部分信息,若是对象是数组,还会额外包含数组长度信息。

1.1.1.标记字

        存储对象自身的运行时数据(即状态),包括哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程id、偏向时间戳等。它们的存储结构相似于C语言中的“位字段”,官方称之为“Mark Word(标记字)”。“标记字”以“字”做为基本的存储单元,即在32位虚拟机中,数据长度为32bit;而在64位虚拟机中,数据长度为64bit。
        以32bit虚拟机为例,有固定的2bit用于存储锁标志位,随着锁标志位值不一样,其余位存储的内容与位长度也不一样。这一点相似于C语言中的联合结构(union),且联合的每个成员都是位字段结构。

1.1.2.类型指针

        类型指针即对象指向它的类元数据(class metadata)的指针,虚拟机经过该指针肯定这个对象是哪一个类的实例。 但须要注意的是,并不是全部虚拟机实现中都会在对象头包含类型指针,也能够采用其余方式保留对象的类型信息

1.1.3.数组长度

        在java中,数组也属于对象,那么理所固然的须要维护数组长度,该信息存放在对象头中。
 

1.2.实例数据

        实例数据即对象的字段(或称为成员变量)存储的数据信息,包含了从父类继承及本身定义的全部字段。且字段在内存中存储的顺序并不等于类中的定义顺序,它受到虚拟机策略的影响(主要考虑到内存对齐以及使用率的问题)。
 

1.3.对齐填充

        相似于C中结构体struct的内存对齐,java对象的内存位置也须要对齐。
        咱们经常使用的Hotspot虚拟机要求每一个对象的起始地址为8字节的整数倍,也就是说,若一个对象结束地址非8字节整数倍,则须要占位符进行填充以保证对齐。
 

2、对象的访问定位

        虚拟机规定,须要经过栈上的“reference(引用)”来操做具体对象。对于该规定,目前有两种主流的实现方式:
  • 经过句柄(handler)实现:该种方式会在堆中划出一块“句柄池”内存空间,每一个栈上的引用直接指向句柄池中的句柄,而句柄中又会维护对象指针和类型指针。使用句柄带来的好处是,栈上的reference存储稳定的句柄地址,GC形成的对象移动只会致使句柄中相应的指向地址改变,而reference地址不改变。

 

  • 经过直接指针(direct point)实现:即在对象的对象头中维护类型指针。栈的reference指向对象,而对象头中的类型指针指向对象类型数据。使用直接指针的好处是,对象的访问速度快,节省了指针二次寻址的开销。
 
 

3、对象的建立过程

        对象的建立过程要经历如下几个阶段:

3.1.类加载

  • 检查到new指令;
  • 虚拟机检查在常量池中是否有该类的符号引用,包括该符号引用表明的类是否已被加载、解析、初始化;
  • 没有,则先加载类(加载过程后续章节会详细讲述);有的话,直接建立对象;

3.2.内存分配

(1)内存分配的方式
         一个对象所需内存在类加载时即可肯定,内存分配方式有两种:
  • 指针碰撞法:若java堆中内存是绝对规整的,全部用过内存都放到一边,空闲的内存放在另外一边,中间放着一个指针做为分界点的指示器,那分配内存就仅仅是把指针向空闲空间那边挪动一段与对象大小相等的距离;
  • 空闲列表法:若堆内存不规整,就没法经过简单的移动指针分配内存。这种状况下虚拟机会维护一个列表记录哪些内存可用,分配时查找并更新列表;
        使用哪一种方式取决于内存是否规整,而内存是否规整又取决于垃圾收集器的GC算法。典型如serial、parnew这两种垃圾收集器,它们在GC时带有压缩整理的功能,所以系统会采用“指针碰撞”的方式分配内存;而CMS这种基于Mark-Sweep(标记-清除)算法的垃圾收集器,则会采用“空闲列表法”分配内存。
(2)内存分配的安全
        须要注意的是,若多个线程同时申请分配内存,若是不加以同步控制,则会致使内存分配不对。不一样的虚拟机会采用不一样的机制避免线程安全问题:
  • 同步锁定:经过CAS配上失败重试的方式保证更新操做的原子性。注:CAS,即CPU硬件同步原语,全称为compare and swap(比较并交换),若比较不对则失败;
  • TLAB:即线程分配缓冲区。在堆中预先为每一个线程分配一小块内存,线程在各自分配的内存上进行内存分配来保证安全。只有当TLAB用尽并申请新的TLAB时,才进行同步锁定。

3.3.内存初始化

        内存初始化指的是将对象分配到的内存全部位重置为0(不含对象头)。若对象经过TLAB分配的,该过程会提早至“内存分配”执行.

3.4.对象头初始化

        设置对象的对象头信息。

3.5.对象实例数据初始化

        设置对象的实例数据信息,即成员变量值。只有这步完成了,一个真正的对象才产生并能提供给咱们使用。
相关文章
相关标签/搜索