对象的创建的过程
当虚拟机遇到一条new指令时,开始进行对象的创建:
将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用。
若没有,说明这个类还没有被定义,会抛出
ClassNotFoundException
.若有,则继续下一步。
检查这个符号引用代表的类是否已被加载、解析、和初始化过
如果没有,那必须先执行类加载的过程。
在类加载通过后,接下来虚拟机将为新生对象分配内存。
根据方法区中该类的信息确定所需的内存大小,
- 对象所需内存的大小在类加载完成后,便可确定。且一个类所产生的所有对象的内存大小是一样的,Jvm在一个类被加载进方法区的时候就知道该类生产的每一个对象所需要的内存大小。
把一块确定大小的内存从Java堆中划分出来给新的对象
分配堆中内存的两种方式:
指针碰撞:如果jvm的垃圾收集器采用
复制算法或标记-整理
算法,那么堆中空闲内存是完整的区域,并且空闲内存和已使用内存之间由一个指针标记。那么当为一个对象分配内存时,只需移动指针即可(指针向空间那边挪动一段与对象大小相等的距离)。空闲列表: 如果jvm的垃圾收集器采用
标记-清除
算法,那么堆中空闲区域交错,因此,虚拟机需要维护一个列表记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
内存分配完后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。
为对象的成员变量赋上初始值。
设置对象头信息。
调用对象的构造函数进行初始化
对象的内存模型
一个对象从逻辑角度看,它由成员变量和成员函数构成;从物理角度来看,对象是存储在堆中的一串二进制数。
- 对象在内存中分为三个部分:
- 对象头
- 实例数据
- 对齐填充
对象头
对象头包含两部分信息:
用于存储对象自身的运行时数据: 如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、等。
类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据。因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组 的大小。
实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中多定义的各类型的字段内容。它就是成员变量的值,其中包含父类的成员变量和奔雷的成员变量。
对齐填充
仅仅起着占位符的作用,由于HotSpot要求对象的总长度必须是8字节的整数倍。由于对象头一定是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐填充字段来补全,确保整个对象的长度为8的整数倍。
对象的访问过程
引用类型的变量中存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式:
句柄访问方式:堆中需要有一块叫做“句柄池”的内存空间,用于存放所有对象 的地址和所有对象所属类的类信息。reference中存储的就是对象的句柄地址,而句柄包含了对象实例数据与类型数据各自的具体信息。
直接指针访问,reference中直接存放对象地址。不需要句柄池。但由于对象的访问在java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本,