在运行时数据区我们了解以后,知道了我们将对应的信息放置到我们逻辑分区的内存中后,接下来就是这些数据是如实创建对象,及其对象的布局,和访问
对象的创建
1.首先在java中,对象似乎通过new
关键字创建的,也就是说通过这个创建指令来创建对象,下面简单画一下执行过程
2. 当类加载完成检查后,需要为对象创建的内存大小区间已经完全确认,为对象分配内存实质上是将大小确定的内存空间从java堆中划分出来。
从java堆分配内存,可能内存是绝对完整的,也可能存在内存不是完整的一整块内存,即已使用内存和未使用内存交错。
- 针对内存绝对完整内存,可以使用
指针碰撞
方式。为新对象分配内存,只需要移动一段空间大小与需要分配内存大小相等的空间对应的指针处即可。
- 针对内存不是完整的,可以使用
空闲列表
方式。当需啊为新对象分配一块较大的内存,需要更新空闲列表状态。如下图所示
关于java堆是否完整,与采用的垃圾回收器是否带有压缩功能有关。例,使用Serial、ParNew等带有压缩功能的收集器系统采用的分配算法是指针碰撞,而使用CMS这种基于标记-清除算法的收集器,采用空闲列表。
- 除了考虑如何分配内存空间之外,还需要考虑并发情况下,如指针碰撞修改指针位置时候,并发会造成不安全。解决这个问题有两个方案
- 对内存空间分配进行同步控制,实际上虚拟机采用CAS(compare and swap)+失败重试保证原子性
- 使用本地线程缓存技术、也就是TLAB(Thread Local Allocation Buffer),也就是每一个线程拥有独立的空间分配内存,可以理解为拥有了内存的隔离性。可以通过设置参数:
-XX:+/-UseTLAB
来控制虚拟机是否使用TLAB
- 内存分配完成后,虚拟机将分配的内存空间都初始化为零,但是不包括对象头。接下来,虚拟机将对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象哈希码、对象的GC分代年龄等信息。这些信息存储在对象头中。
- 当上述工作都完成后,虚拟机创建对象步骤已经完成,所有的字段都已经初始化为零。紧接着执行new指令后的
<init>
方法,也就是程序中的构造函数,按照程序员的意图对程序进行赋值,这样一个真正的对象创建完成。
对象的内存布局
- 在hotsopt虚拟机中,对象的内存存储划分为三块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding),下面简单描述一下存储的关系
对象的访问定位
- 建立对象以后,通过栈上的reference数据来操作堆栈的具体对象。目前主流的访问方式有句柄和直接指针两种
- 使用句柄访问
在java堆中、划出一块内存空间来作为句柄池,句柄池,reference指向的对象句柄的地址,句柄包含了两种信息的地址、一是:对象实例数据的地址。而是:对象类型数据的地址。
- 直接指针,reference直接指向对象地址
- 以上两种方式各有各自的优势,使用句柄访问好处reference存储的句柄地址无需修改,只需要修改对象数据指针的地址。使用直接指针的好处是节约一次reference指针的访问,优点是访问更快。