返回顶部

深入理解JVM(4)——对象的创建和访问

1、对象的创建

在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已。

在虚拟机中,对象(文中讨论的对象限于普通 Java 对象,不包括数组和 Class 对象等)的创建过程如下:

1.1、分配内存

空间分配的两种方式

  • 指针碰撞:当已分配空间被集中存放,已分配和未分配空间使用一个指针来标记时,分配新的空间只需要移动该指针即可,此方法为指针碰撞。适用于 GC 算法会做 Compact 的情况。
  • 空闲列表:当已分配的空间是分散存放时,虚拟机必须维护一个记录了哪些内存块是可用的列表,此为空闲列表,需要分配新空间时只需要从该列表中获取。

分配方式的选择取决于由Java堆是否规整,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 因此,在使用 Serial、 ParNew 等带 Compact 过程的收集器时,系统采用的分配算法是指针碰撞,而使用 CMS 这种基于 Mark-Sweep 算法的收集器时,通常采用空闲列表。

解决空间分配线程安全问题的两种方式

  • CAS方式失败重试:顾名思义,遇上分配时的线程冲突时,会再次进行空间分配直至成功。实现简单直观但是效率较低
  • 空间划分:为每个线程分配单独的一块空间,该空间只用来给该线程做创建对象分配空间时使用,这单独的空间被称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。当该线程的 TLAB 分配光了后,才需要同步锁定,效率较高。是否使用TLAB可以通过虚拟机参数 -XX:+/-UseTLAB 指定。

 

2、对象的内存布局

 

2.1、设置对象头

普通对象头包含两部分信息:

  • 运行时数据(Mark Word):hash码、GC分代年龄、锁状态标志; 线程持有的锁;偏向线程ID、 偏向时间戳等。
  • 类型指针 (Klass Point):用来说明该对象是哪个类的实例。指向 方法区(元空间/永久代)中 Class 类型数据。

数组类型的对象,则头信息里还有 Length 信息。

在32位的虚拟机中,不同的锁状态对应的 MarkWord 的字节具体分配:

锁状态

25 bit

4 bit

1 bit

是否为偏向锁

2 bit

锁标志位

23 bit

2 bit

无状态

标记对象的 hashCode

分代年龄

0

01

偏向锁

线程 ID

epoch(时间戳)

分代年龄

1

01

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向互斥量(重量级锁)的指针

10

GC 标记

11

其中无锁和偏向锁的锁标志位都是 01,只是在前面的 1bit 区分了这是无锁状态还是偏向锁状态

2.2、设置对象实例数据

对象的实例数据主要是根据 JAVA 代码的编写生成的,包括父类在内的各种类型的字段,其存储顺序受虚拟机分配策略参数及代码中字段定义顺序的影响。

虚拟机安排字段的方式:

  1. 相同宽度的字段放在一起
  2. 父类的字段放在子类的前面
  3. 窄小的变量也会被安排在父类的字段空隙中(C++的内存安排规则,HotSpot VM是由C++语言编写)

2.3、填充字段

HotSpot VM要求对象的起始位置必须是8字节的整数倍,也就是说对象必须是8字节的整数倍,所以需要填充占位(这也是一句C++的规则来的)。

3、对象的访问

 JAVA 程序访问对象需要通过栈上的 reference 数据操作堆上的具体对象。reference 对对象的访问方式主要为句柄和直接指针两种。

句柄访问

Java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。

直接指针访问

reference中存储的直接就是对象地址

句柄访问方式的好处是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。

直接指针访问方式的好处是速度更快,它节省了一次指针定位的时间开销。

虚拟机Sun HotSpot 使用的是直接指针访问。 

 

posted @ 2018-12-04 19:47  jaden好青年  阅读(187)  评论(0)    收藏  举报