在前一篇文章中我们学习了Java虚拟机的结构原理与运行时数据区域,那么我们大概知道了Java虚拟机的内存的概况,那么内存中的数据是如何创建和访问的呢?这篇文章会给你答案。
对象的创建通常是通过new一个对象而已,当虚拟机接收到一个new指令时,它会做如下的操作。 (1)判断对象对应的类是否加载、链接、初始化 虚拟机接收到一条new指令时,首先会去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被类加载器加载、链接和初始化过。如果没有则先执行相应的类加载过程。关于类加载器我们在前一篇文章中已经提到过,这里不再赘述。
(2)为对象分配内存 类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:
Java堆的内存是否规整根据所采用的来及收集器是否带有压缩整理功能有关,关于垃圾收集器,本系列后面的文章会介绍。
(3)处理并发安全问题 创建对象是一个非常频繁的操作,所以需要解决并发的问题,有两种方式:
(4)初始化分配到的内存空间 将分配到的内存,除了对象头都初始化为零值。
(5)设置对象的对象头 将对象的所属类、对象的HashCode和对象的GC分代年龄等数据存储在对象的对象头中。
(6)执行init方法进行初始化 执行init方法,初始化对象的成员变量、调用类的构造方法,这样一个对象就被创建了出来。
对象创建完毕,并且已经在Java堆中分配了内存,那么对象在堆内存是如何进行布局的呢? 以HotSpot虚拟机为例,对象在堆内存的布局分为三个区域,分别是对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
对象的内存布局如下图所示。
HotSpot中采用了OOP-Klass模型,它是用来描述Java对象实例的一种模型,OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。 HotSpot中,用instanceOopDesc 和 arrayOopDesc 来描述对象头,其中arrayOopDesc对象用于描述数组类型。 instanceOopDesc的代码如下所示。 openjdk/hotspot/src/share/vm/oops/instanceOop.hpp
可以看出instanceOopDesc继承自oopDesc: openjdk/hotspot/src/share/vm/oops/oop.hpp
oopDesc中包含两个数据成员:_mark 和 _metadata。其中markOop类型的_mark对象指的是前面讲到的Mark World。_metadata是一个共用体,其中_klass是普通指针,_compressed_klass是压缩类指针,它们就是前面讲到的元数据指针,这两个指针都指向instanceKlass对象,它用来描述对象的具体类型。instanceKlass的代码如下所示。
openjdk/hotspot/src/share/vm/oops/instanceKlass.hpp
instanceKlass继承自Klass ,枚举ClassState 用来标识对象的加载进度。知道了OOP-Klass模型,我们就可以分析Java虚拟机是如何通过栈帧中的对象引用找到对应的对象实例,如下图所示。
从图中可以看出,通过栈帧中的对象引用找到Java堆中的instanceOopDesc对象,再通过instanceOopDesc中的元数据指针来找到方法区中的instanceKlass,从而确定该对象的具体类型。