JVM之对象

starlin 1,015 2018-09-02

之前已经大概介绍过了虚拟机内存的情况,这篇文章来看看在虚拟机里面对象时如何创建的

对象的创建

在Java语言中,一般我们创建一个都是用new关键字,那么虚拟机遇到new关键字是如何处理的了

首先虚拟机回去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过。如果没有,那必须先执行相应的类加载过程(这个类加载过程后面会介绍)
类检查通过后,接下来虚拟机将为新生对象分配内存。对象所需要的内存大小在类加载完成后就能确定了。

假设Java对中的内存是规整,所有用过的内存都放一边,空闲的内存放一边,中间放着一个指针作为分界点的指示器,这种分配方式称为“指针碰撞”。
如果Java堆中的内存不规整,则虚拟机内部维护着一个代表Java堆中那些内存块是可用的的列表,若列表中的某些块已分配给对象实例,那么就会更新列表上的记录,我们称这种分配方式为“空闲列表”。选择何种分配方式由Java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
因此,在使用Serial,ParNew等带Compact过程的收集器时,系统采用的是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表

内存分配完成后,接下来虚拟机需要对对象进行必要的设置,例如这个对象是那个类的实例,如果才能找到类的元数据信息、对象的 哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头之中

上面的工作都完成后,从虚拟机的视角来说,一个新的对象已经产生了,但从Java程序的角度来看,对象创建才刚刚开始-----方法还没有执行,所有的字段都还是零,所以一般来说,执行new指令后接着会执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的内存布局

在HotSpot虚拟机中,对象在内存中的存储布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)

HotSpot虚拟机对象头包含两部分信息:

  1. 第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志、线程持有的锁、偏向线程ID,偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称为“Mark Word”,在虚拟机中“Mark Word”被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如,在32位的HotSpot虚拟机中,如果对象处于未被锁定的状态下,那么Mark Word的32bit中的25bit用于存储哈希码,4bit用于存储对象的分代年龄,2bit用于存储锁标志位,1bit固定为0,而在其他状态(轻量级锁定、重量级锁定、GC标志、可偏向)下对象的存储状态如下表:
存储内容标志位状态
对象哈希码、对象分代年龄01为锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10重量级锁定
空,不需要记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向
  1. 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象时那个类的实例。并不是所有的虚拟机都必须在对象数据上保留类型指针。

接下来的实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容,这部分的存储顺序会收到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中的定义顺序影响。HotSpot虚拟机的默认分配策略为longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Pointers),从分配策略可以看出,相同宽度的字段总是被分配到一起

最后一部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅是起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的倍数,也就是对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数,因此,当对象实例的数据部分没有对齐时,就需要通过对齐填充来不全。

对象的访问定位

建立对象就是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象,目前主流的访问方式由使用句柄和直接指针两种

  1. 句柄访问,Java堆中将会划分出一块内存来作为句柄,reference中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的具体信息,如下图所示:

  2. 指针直接访问,你们Java堆对象的布局中就必须考虑如何放置访问数据的相关信息,而reference中存储的直接就是对象地址:如下图所示:

这两种访问方式各有优势,使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只会改变句柄中的实例数据指针,而reference本身不需要修改
使用直接指针访问的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此此类的开销积少成多后也是一项非常可观的执行成本,对于HotSpot虚拟机而言,它是使用第二种方式进行对象的访问

参考

深入理解Java虚拟机


# JVM