Java虚拟机对象创建、布局及访问

对象的创建

        在语言层面上,创建对象(复制、反序列化)除外,仅仅是一个new 关键字的操作,而当虚拟机遇到一个字节码new 指令时,首先将去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,那必须先执行相应的类加载过程。

        1.在类检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存在类加载完成后(运行期)便可以完全确定,为对象分配内存等同于于把一块确定大小的内存块从Java堆中划分出来。分别有两种分配方式:

         ①.指针碰撞:假设Java内存划分是规整的,分配的放一边,空闲的放另一边,那么分配内存时仅仅是把指针向空闲区域移动一段距离(与对象等长)。

        ②.空闲列表:假设内存是不规整的,虚拟机就必须维护一个列表,记录内存状态,分配时找到足够大的区域划分给对象实例,并更新列表上的记录。

        选择哪种方式由Java堆是否规整决定,而Java堆是否规整又与所采用的垃圾收集器是否带有空间压缩整理的能力决定。因此当使用Serial、parNew等带压缩整理过程的收集器时,采用的是指针碰撞,当使用CMS这种基于清除算法的收集器时,只能采用较为复杂的空闲列表来分配内存。

         2.在内存分配时,需要考虑并发其情况,即当给对象A分配对象时,指针尚未修改完成,虚拟机又用原来的地址进行修改。解决上述问题有两个办法:

          .虚拟机采用CAS配上失败重试的方式保证更新操作的原子性

          .把内存分配的动作按照线程划分在不同的空间进行,即每个线程Java堆中预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer ,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有缓冲区用完了,分配新的缓冲区时才需要同步锁定,虚拟机是否使用TLAB这种方式,可以通过-XX:+/-UserTLAB参数来决定。

          3.分配完内存后,虚拟机需要将分配到的内存空间都初始化为0值,如果采用TLAB方式,也可以提前至TLAB分配时进行。这步操作保证了对象的实例字段在Java代码中可以不赋初始值就能直接使用,使程序能访问到这些字段的数据类型所对应的0值。

          4.对对象进行必要设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分带年龄到等信息,这些都在对象头中,后面具体介绍。

          5.调用构造函数,上面所有的工作都完成后,从虚拟机的角度来看一个新的对象已经产生,但是从Java程序的视角来看,对象的创建才刚刚开始-----构造函数,即Class文件中的<init>()方法还没有执行,所有的字段都为默认的零值,new指令之后会接着执行<init>()方法,按照程序员的意愿去初始化,至此一个对象被完全的构造出来。

对象的内存布局

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

         HotSpot对象头部分包含两类信息,第一类用于存储对对象自身运行时的数据,如哈希码、GC分代年龄段、锁状态信息、线程持有的锁、偏向线程Id、偏向时间戳等。这部分数据统称为“Mark Word”,考虑到虚拟机空间效率,Mark Word被设计成一个有着动态定义的数据结构,以便在极小的空间内存储尽量多的数据。

Mark Word结构如图所示:

Java虚拟机对象创建、布局及访问

        对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java通常通过这个指针来确定该对象是哪个类的实例。此外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

       实体数据是真正的有效数据,是在我们程序代码里面定义的各种类型的字段内容,无论是父类的还是子类的都必须记录下来。

       对齐填充是第三部分,不是必然的,仅仅启着占位符的作用。

对象的访问

        对象的方位分为句柄访问直接访问

        ①如果使用句柄访问,Java堆中将可能划分出来一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。优点是:当对象被移动时(被垃圾回收)只会改变句柄中的实例数据指针,而reference本身不会改变。

Java虚拟机对象创建、布局及访问

       ②如果使用直接访问,Java堆中的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只访问对象本身,就不需要多一次间接访问的开销。优点是:速度快,节省了一次指针定位的时间开销。

Java虚拟机对象创建、布局及访问