HotSpot虚拟机对象探秘

1、对象的创建

1)判断类是否已被加载、解析和初始化

当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2)为新生对象分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可以完全确定,为对象分配内存的任务实际上便等同于把一块确定大小的内存块从堆中划分出来。

分配内存会出现两个问题:如何分配的问题和并发安全的问题。

如何分配的问题:如果内存连续,采用指针碰撞分配;如果内存不连续,采用空闲列表分配。

并发安全的问题:采用CAS配上失败重试的方式或采用本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)的方式。

3)初始化分配到的内存空间

内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值。这步操作保证了对象的实例字段在Java代码中可以不赋初值就能直接使用。

4)设置对象的对象头

接下来,Java虚拟机对对象进行必要设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,把这些信息存放在对象的对象头(Object Header)中。

5)执行init方法进行初始化

从虚拟机的视角来看,一个新的对象已经产生。但是从Java程序的视角来看,对象创建才刚刚开始——构造函数,即Class文件中的()方法还没有执行,所有的字段都为默认的零值,对象需要的其他资源和状态信息也没有按照预定的意图构造好。new指令之后接着执行()方法,按照程序员的意愿对对象进行初始化。

2、对象的内存布局

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

1)对象头

对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。第二类是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。

2)实例数据

实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。

3)对齐填充

对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。

3、对象的访问定位

创建对象自然是为了后续使用该对象,Java程序会通过栈上的reference数据来操作堆上的具体对象。对象访问方式是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

1)如果使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息,其结构如下图所示:

2)如果使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销,其结构如下图所示:

这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销。HotSport采用直接指针访问的方式。

你可能感兴趣的:(HotSpot虚拟机对象探秘)