JVM中的对象

对象的创建

对象创建的过程
image.png

类加载检查

当虚拟机遇到一条 new 指令时,会先去检查这个指令的参数在运行时常量池中是否能够定位到这个类的符号引用,并检查这个符合引用代表的类是否已经被加载、解析和初始化过。若没有,则JVM会先执行相应的类加载操作

分配内存

类加载检查通过以后,虚拟机会为当前对象分配内存,对象所需要的内存大小通常在类加载完成后便可以确定;JVM会在堆中划分出一块确定大小的内存

分配内存的方式

  • 指针碰撞:适用于堆内存规整,没有内存碎片的情况下,通常使用的是Serial、ParNew 收集器,采用标记-复制标记-整理算法;指针只需要从已经分配的内存边界向没有分配内存的边界移动对象内存大小的位置即可
  • 空闲列表:适用于堆内存不规整,有内存碎片的情况下,通常使用的是CMS收集器,采用标记-清除算法;虚拟机会维护一个列表,列表中会记录哪些内存块是可用的,并找出一个足够大的内存分配给对象,最后再更新内存列表

    分配内存时的并发问题

    虚拟机分配内存时通过以下两种方式保证线程安全:

  • CAS+失败重试:Compare And Swap 是一种无锁算法,是乐观锁的一种;每次不加锁去完成某项操作,若操作失败就会重试,直到成功为止。

  • TLAB:Thread Local Allocation Buffer,即线程本地分配缓存,是一个线程专用的内存分配区域;为每一个线程预先在Eden区分配一块内存,JVM在给对象分配内存时,首先在TLAB区域分配,当对象大于TLAB中的剩余内存或者TLAB已经用尽时,才会使用CAS+失败重试进行内存分配

    初始化零值

    内存分配完成以后,虚拟机需要将分配到的内存空间都初始化成零值(不包括对象头),保证了对象的字段在不赋初始值(全局变量)就可以使用

    设置对象头

    初始化零值以后,JVM会对对象进行必要的设置:

  • 设置类的元数据信息(用来描述数据的数据,如类的版本信息)

  • 对象的哈希码
  • 对象的GC分代和年龄信息

    执行 init() 方法

    执行完以上操作后,JVM还需要执行 init 方法,相当于构造方法,为相关字段按照程序员的设置赋值,这样一个可用的对象才被生产出来

    对象的内存布局

    在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头实例数据对齐填充

    对象头

    对象头主要包括两部分信息:

  • 自身的运行时数据(Mark Word):哈希码、GC分代年龄、锁状态标志

  • 类型指针:指向对象的类元数据指针,用来确定对象的所属类

    实例数据

    实例数据就是对象真正存储的有效信息,即程序中所定义的各个类型字段的内容

    对齐填充

    对齐填充不是必要存在的,仅仅起到占位的作用
    因为JVM要求对象的大小必须是8字节的整数倍,其中对象头正好时8字节的1倍或2倍,当对象实例数据部分不满足8字节的整数倍时,就会通过对齐填充来补全

    对象的定位访问

    Java 程序是通过虚拟机栈上的 reference 来操作堆上的对象的,主要是通过使用句柄或是直接指针来实现的

    句柄

    JVM会在堆内存中划分一块作为句柄池,reference 中存放的是句柄的内存地址,句柄中又包含了对象实例数据和类型数据的具体地址信息;reference 的指向是稳定的,只需要维护句柄池中的信息

    直接指针

    reference 中存放的是对象的地址,需要在栈中去维护 reference