内存中对象的创建、对象的结构以及訪问方式。

一、对象的创建

在语言层面上。对象的创建仅仅只是是一个newkeyword而已,那么在虚拟机中又是一个如何的过程呢?

(一)推断类是否载入。虚拟机遇到一条new指令的时候,首先会检查这个指令的參数能否在常量池中定位到一个类的符号引用,而且检查这个符号代表的类是否被载入、解析并初始化。假设没有完毕这个过程,则必须运行对应类的载入。

(二)在堆上为对象分配空间。对象须要的空间大小在类载入完毕后便能确定。之后便是在堆上为该对象分配固定大小的空间。

分配的方式也有两种:

i. 第一种假设使用SerialParNew等带Compact过程的收集器的时候,Java内存中的堆都是规整的。仅仅需把作为使用和未使用空间的分界点的指针移动一段距离就能够了。

ii. 另外一种假设使用CMS这样的基于Mark-Sweep算法的收集器的时候,Java内存并非规整的,虚拟机就要维护了一个列表来记录内存的使用情况。这样的方式叫做“空暇列表”的方式。

虚拟机为对象分配空间是很频繁的,假设同一时候为多个线程分配对象。就涉及到并发安全控制了。一般有两个解决方式:

(1)第一种是对分配内存空间动作进行同步-使用CAS配上失败重试的方式保证更新操作的原子性。

(2)另外一种是把内存分配的动作分配在不同的空间中进行,既每一个线程在Java堆中预先分配一小块内存,称之为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。

哪个线程要分配内存。就在哪个线程的TLAB上分配。仅仅有TLAB使用完并须要分配新的TLAB的时候才须要同步锁定。

(三)初始化内存空间。内存分配完毕之后,虚拟机会将分配空间内都初始化为零值(不包含对象头),假设使用TLAB分配,这一过程也能够提前至TLAB分配时进行。

(四)设置对象的对象头

接下来虚拟机要设置对象的对象头。包含对象的哈希码、类元素信息、GC分代年龄等。

这些信息都放置在对象头中。

(五)运行<init>方法。初始化对象内成员。

运行完这五步,一个对象才算是真正产生。

二、对象的内存布局

内存中,对象存储布局可分为三部分:对象头(Header)演示样例数据(Instance Data)对齐填充(Padding)

i. 对象头:包含两部分信息。第一部分用于存储对象自身的执行时数据,如哈希码,GC分代年龄、锁状态、线程持有锁、等等。

这部分数据的长度在32为或64位,官方称之为“Mark Word”。

对象头的还有一部分是类型指针。即对象指向它的类元素的指针。通过这个指针来确定这个对象时那个类的实例。

(假设Java对象时一个数组,则对象头还必须有一块用于记录数组长度的数据。

由于Java数组元数据中没有数组大小的记录)

ii. 实例数据:这部分是真正用来存储对象有效信息的地方。

iii. 对齐填充:这部分并非必需存在的,仅仅是起着占位符的作用。

由于HotSpot虚拟机要求对象起始地址必须是8字节的倍数。

三、 对象的訪问方式

我们能够通过使用栈上的reference数据来操作堆上的详细对象。有两种方式来訪问详细对象:句柄和直接指针。

句柄Java堆中划分出一个句柄池。专门用来存放对象的实例地址和类型地址。而栈中的reference仅仅是该句柄池中某一句柄的地址。优点是当进行垃圾回收并被移动后。对象地址改变而reference的数据不用改变。

直接指针reference直接指向某一对象的地址。

优点便是速度快。节省了一次定位的时间开销。

posted on 2017-07-09 11:48  yutingliuyl  阅读(139)  评论(0编辑  收藏  举报