Java虚拟机学习(2)—对象的创建、布局、访问

创造的神秘,有如夜间的黑暗,是伟大的。而知识的幻影,不过如晨间之物。 ——泰戈尔

在了解了虚拟机内存区域划分之后,我们就可以更进一步探究Java对象是如何在内存上创建、存放并访问的了。

1、对象的创建


当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析、初始化过。如果没有,则首先进行类加载。

当类加载完成后,新生对象所需的内存就已经可以确定了。所以为新生对象分配空间就是从堆空间中划分一块大小确定的内存块。堆的内存分配有两种方法,一种分配方式叫“指针碰撞”,在堆内存空间规整(没用的在一侧,用过的在另一侧)时,移动内存指针即可划分一块未用的内存给新生对象;而当已使用和未使用的内存交缠在一起时,只能维护一个列表来记录每块内存块的位置以及是否可用,这种分配方式叫做“空闲列表”。而内存是否规整取决于内存收集算法与垃圾收集器是否带有空间压缩整理的能力。

内存分配还存在一个问题,即线程安全问题。因为创建对象是一件很频繁的事情,如果不保证内存分配操作的原子性,很容易出现内存泄漏。为了解决这个问题,也有两种方法:一种是对内存分配进行同步处理;另一种是为每一个线程划分一块内存缓冲区域(TLAB),当线程需要分配内存时,优先从自己的内存区域分配,当需要的内存大于缓冲区所剩内存时,才同步锁定。

分配完内存后将所有内存空间(不包括对象头)设为零值,这也就是为什么局部变量必须赋初值,而实例变量可以不赋初值就可以使用。如果采用TLAB分配内存,设零值可以提前至分配内存时完成。

接下来,虚拟机会对对象进行一些必要的设置,将对象的类信息、类的元数据信息、对象的哈希码、对象的GC分代年龄等存放在对象头之中。

最后,调用构造函数,即Class文件中的< init >()方法,这样,一个新生对象就算创建完成了。

2、对象的布局

在堆内存中,对象的储存可以分为三个部分:对象头、实例数据、对齐填充。

对象头分为两类信息,一类是对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程池有的锁、偏向线程ID等,一类是类型指针,指向对象所对应的类型的元数据。此外,如果对象是数组,对象头中还有一部分用来记录数组的长度信息。

实例数据时对象真正存储的有效信息,即我们定义的各种实例变量。无论是从父类继承的,还是本身定义的,都会存放在实例数据中。

对齐填充无实义,只起占位符的作用。由于HotSpot要求对象起始地址必须是8字节的整数倍,因此一个对象不足的部分,用对齐填充来补齐。

3、对象的访问定位

Java程序通过栈上的reference来操作堆上的具体对象。主流的reference访问对象方式也有两种:

  1. 使用句柄访问:Java堆中划分一块内存作为句柄池,reference中存放对应句柄的地址,而句柄既存放对象实例数据地址,也存放对象类型数据地址。

  2. reference中存放Java堆中对象的地址,而对象本身负责存放对象类型数据。

如下图:

这两种方式各有优劣,句柄访问少一次指针定位,时间开销小;而句柄访问最大的优势就是reference储存的地址稳定,不会因对象被移动(垃圾收集时经常移动)而改变。
HotSpot采用直接指针访问。

更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值