《深入理解Java虚拟机》读书笔记6-解密HotSpot中的实例对象

时间:2023-01-02 11:40:25

解密HotSpot虚拟机中的对象

  介绍完class类文件以及它的加载过程,接下来我们看看class类对应的实例对象的秘密。

1、对象的创建

         当虚拟机遇到一条new指令时,首先检查指令后面参数是否能在常量池中对应一个符号引用,然后再检查符号引用对应的class类是否已经加载、解析和初始化过,如果没有则触发该class类的类加载过程(即上一节的《虚拟机加载class文件》)。当类加载检查通过之后,就会给实例对象分配内存空间(一个对象占多大内存空间在类加载完就可以确定的)(2018.12.15补充:除了new关键词以外,遇到字符串字面量若该字符串之前没有过,则也会新建一个String对象;有些自动装箱操作也可能新建一个基础类型对于的对象类型;遇到“+”(不是作为常量表达式)作为字符串连接符,一定会创建一个新的String对象。)分配内存空间有两种可能的情况:

  • l  内存空间是连续规整的,那么分配内存空间就只需要把空闲区和已分配区中间的分界指针向空闲区移动对象内存大小的空间。这种分配方式叫:指针碰撞
  • l  内存空间不是规整的,而是散布的,那必须有一张表来记录哪段空间空闲,哪段被占用,分配空间则是查询记录表,并把合适的区域在表中更新为被占用状态。这种分配方式叫:空闲列表

采取那种分配方式要看内存是否规整,而内存是否规整又要看采取的垃圾回收算法是否会把回收的空间compact。(垃圾回收机制之后再说~)

         除了考虑分配方式之外,还要考虑的是分配过程是否线程安全。在实际使用中,肯定不止一个线程会触发对象的创建,而不论使用指针碰撞还是空闲列表,分配操作都不是原子操作,所以需要额外的手段来保证线程安全。也有两种方式:

  • l  CAS算法配上失败重试:CAS(Compare and swap)就是在要更新一个值前,先看看这个值当前状态是否是我预期的状态,如果是则可以更新,如果不是则说明该值被改动过,更新失败(这篇博客讲得蛮清晰的https://blog.csdn.net/liubenlong007/article/details/53761730 ) 。
  • l  给每个线程预先分配一块内存区域,每个线程都能直接在各自独有的这块内存里分配对象内存。线程独有的这块内存区域叫本次线程分配缓冲(TLAB

  在内存分配完之后,就需要给内存空间都初始化为零值,这样才能保证实例对象没赋值之前就可以使用。

  之后就是设置对象头的内容,包含类型元数据信息、对象哈希码等。下文详细介绍对象头。

  最终就是执行<init>初始化方法,即程序员的构造器内容了。至此一个对象就创建完成了。

  (2018.12.15补充:对象的实例化顺序

  1. 分配所有实例变量的内存区域,并将内存空间初始化为零值,包括父类的实例变量
  2. 执行构造器方法,先将构造器的参数值分配给本地变量表中的参数变量
  3. 如果该构造器方法引用了其他某个构造器方法(通过this()),则递归调用,如果中途出错,则退出并抛出异常
  4. 如果不是Object类且没有引用其他构造器方法,则隐式或显式地调用父类的构造器方法,同理若是中途出错则抛出异常
  5. 然后按定义顺序执行实例变量的赋值操作语句和初始化块,
  6. 然后执行构造器的后续操作

此外,若父类构造器里调用一个被子类覆盖的方法,则实际调用的会是子类里的方法实现。见https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5)

2、对象的内存布局          

         对象在内存中的区域可划分为三块:对象头、实例数据、对齐填充。

         对象头中存储了对象的元数据信息。对象头一般可分为两部分:

  • l  Mark word:在32位64位虚拟机中分布占32位和64位,其中两位作为标志位,根据标志位的不同后面的位又代表不同含义,具体可以包含:对象哈希码、GC分代年龄、锁状态、线程持有锁等。
  • l  类型指针:即指向它类元数据的指针。(其实这一部分并不是一定有,当对象通过句柄池访问(见下文)时,这部分并不在对象数据中。Hotspot是采用直接访问,所以有)。

当对象时数组时,对象头中还需要一块用来记录长度。

         实例数据即一个对象所有的实例字段内容,包括父类继承下来的。储存顺序受分配策略和定义的顺序影响。HotSpot默认分配策略是:longs/doubles.ints,shorts/chars,bytes,Booleans,oops(Ordinary Object Pointers)(相同宽度的分配到一起。

         填充对齐是为了保证对象大小必须是8字节的整数.

3、对象的访问定位

         在方法栈中只存了reference类型的数据,即一个对象的引用.这引用如何去定位对象,目前有两种方式.

  • 1) 句柄访问:优点是对象位置移动时(例如垃圾收集时),只需改变句柄中的实例数据指针,reference中存储稳定.(此时实例对象里面就没有类元数据的指针)

 《深入理解Java虚拟机》读书笔记6-解密HotSpot中的实例对象

 

  • 2) 直接访问:优点是减少了一次指针定位过程(HotSpot采用)

《深入理解Java虚拟机》读书笔记6-解密HotSpot中的实例对象