Java虚拟机(二)HotSpot虚拟机对象探秘

对象的创建

在Java中,一个对象的创建,一般是使用new指令去完成。而c++则是直接定义对象就行了,Java虚拟机在对对象的创建和使用中有着自己的分配机制。我们的虚拟机遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,执行相应的类加载。类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的空闲内存中划分一块区域。

内存的划分方式有两种第一个是:‘指针碰撞-内存规整’。就是在内存空间中,有已使用内存和未使用内存,它们是整齐排列,有分界线的,当我们需要新的内存空间的时候,指针就会移动一小位内存空间去分配。第二种是:‘空闲列表-内存交错’的分配方式,在内存空间中,所有的内存呈现散列分布,需要通过一个表去记录哪里可以使用,哪里不可以使用。

内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。new 指令后执行,执行完init 方法才算一份真正可用的对象创建完成。

对象的内存布局

我们一般使用的Java虚拟机,叫做HotSpot虚拟机。在 HotSpot 虚拟机中,分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头(Header):包含两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。

实例数据(Instance Data):程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

对齐填充(Padding):不是必然需要,主要是占位,保证对象大小是某个字节的整数倍。HotSpot虚拟机自动内存管理系统要求对象起始地址必须是8字节的整数倍,不够则进行填充。

对象的访问定位

使用对象时,通过栈上的 reference 数据来操作堆上的具体对象。但是 reference 并没有规定使用什么方式去引用,所以是取决于虚拟机的实现而定的,目前主流的方法有两种:

通过句柄访问

Java 堆中会分配一块内存作为句柄池。reference 存储的是句柄地址。

jvm3

这个句柄池,可以看做为是一个有完整分类的区域,需要的各个对象,会不断地通过这个分类区域,通过一步步分类寻找,找到所需对象,可见性比较好。

直接指针访问

reference 中直接存储对象地址。

jvm4

这种方式是使用指针去直接找到地址,好处就是直接寻址的方式更有利于非常频繁的访问,不好的地方在于可见性不强。这个两种方法各自有各自的好处。通过句柄的方式,在进行GC的时候,由于有一个大致的分类,GC比较快速,但是在访问的时候,花费时间比较长,而通过地址的方式则反之,它在进行频繁访问的的时候有极高的效率,但是在GC的时候,效果不佳。就单论HotSpot而言,是使用第二种方式去实现的。