JVM学习【二】---对象的内存布局

两盒软妹~` 提交于 2020-03-05 02:29:15

二、对象(实例)的内存布局

1.对象的创建

  1. 虚拟机遇到一个new指令
  2. 检查这个指令的参数是否能在常量池中定位到一个类的符号引用
  3. 检查这个符号引用代表的类是否已经被加载、解析、初始化过。
  4. 如果没有,则执行类加载过程。(如果有,直接为新对象分配内存)
  5. 类加载检查通过后,vm为新生对象分配内存。(对象所需要的内存大小,在类加载完成后就能完全确定)
  6. 根据不同情况选取,内存分配策略:1)指针碰撞。2)空闲列表。(采取哪种分配方式由内存是否规整决定,内存是否规整由GC策略决定)
    1. 指针碰撞:堆中的内存是足够规整的,占用中内存和空闲内存以一个指针为分割,各占内存的一边。此时分配内存,只需要移动指针到对象大小的距离。这种分配方式叫做指针碰撞。
    2. 空闲列表:堆中的内存不规整,无法通过一个指针去标识,vm必须维护一个空闲内存列表,在分配时找出一块足够大的内存去分配,并更新列表记录,这种分配方式叫做空闲列表。
  7. 在TLAB中分配空间。(TLAB本地线程分配缓存,用于解决并发问题,线程1给A分配内存,刚分配完,指针还没修改或是列表还没更新,线程2又使用了刚刚的指针分配给B了内存,为了保证内存分配的安全,每个线程都预分配了一个独立的内存缓冲区,每个线程分配内存时,都在自己的缓冲区内存里分配,等缓冲区内的内存用完了,再使用同步锁定保证原子性。)
  8. 内存分配完成后(指针移位,列表同步),已分配的内存空间都初始化为0。如果使用TLAB,初始化可提前到TLAB分配时进行。(这一步骤保证了java实例中的成员变量【基础类型的字段】,可以不赋值就使用,我们可以访问这些数据类型所对应的零值)
  9. 这之后,虚拟机对对象会进行一些”初始“的设置:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、GC分代年龄(这些信息存在对象头)。根据vm运行状态,是否启用偏向锁等等。。
  10. 此时对于vm来说,一个对象已经”创建成功“了。而对于java程序来说,创建才刚开始—方法还没执行,所有的字段属性还是0。接下来根据我们所构造的参数,来对实例进行初始化。这样一个对象那个才真正的创建成功了。

2.对象的内存布局

  • 在HotSpot VM中,对象在内存中的存储可以分为三块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
  1. 对象头(Header)包括两部分信息:1)对象自身运行时数据(哈希码、GC分代年龄、锁状态标志、线程锁、偏向线程ID、偏向时间戳),官方称这部分数据为Mark Word。2)类型指针(对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例) 根据数组的元数据,无法确定数组实例的长度,所以需要在对象投中增加一个数组的长度
  2. 实例数据(Instance Data)存储的是对象真正存储的有效信息,对象内各个成员字段的值。
  3. 对齐填充不是必然存在的,仅仅起着占位符的作用。(对象大小必须是8字节的整数倍,对象头部分刚好是8字节的倍数)

3.对象的访问定位

java中,在栈中定义一个reference类型对象,在堆中定义对象实体,通过栈中的reference对象指向堆中实体来控制对象。jvm只规定了指向对象的引用,并没有定义使用何种方式去定位堆中对象的具体位置。

目前主流方式:1)句柄。2)直接指针。

  1. 句柄:堆中划分一块内存作为句柄池,reference(栈中)中存储对象的句柄地址,句柄中包含对象实例数据(堆中实例池)与类型数据各自的地址信息(方法区)。
  2. 直接指针:reference(栈中)直接存储,对象实例数据(堆中)的地址,对象实例数据中存放类型数据地址(指向方法区)。

各自优势:

  1. 句柄:reference中存放的是稳定的句柄地址,对象被移动时,只需要改变句柄中的对象地址(实例对象指针),不需要改变reference。
  2. 直接指针:速度更快,节省一次指针定位的开销。HotSpot VM使用【直接指针】定位对象。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!