Java对象创建—深入理解Java虚拟机(四)

前言

 Java是一门面向对象的编程语言,无时无刻不在创建对象,因此,对象的创建过程,我们有必要窥伺一二。这样我们才能真正理解这门语言的精髓所在与奥妙之处。

正文

1.Java对象的创建
 Java的创建并不是我们在使用时,采用new关键字这么简单而已,当虚拟机遇到了一条new指令以后,会先去检查是否能够在常量池定位到一个类的符号引用,然后检查类是否已经被加载,解析,初始化过,这部分知识在介绍类加载器时会提到的过程。
 在类加载检查以后,虚拟机开始为这个新生的对象分配内存。对象所需要的内存大小在类加载完成以后就确定了,为对象分配内存其实很容易理解,因为对象是存放在堆中的,我们首先根据需要内存的大小去堆中规划内存,然后把对象放进去,事实上情况情况可能还要复杂一点。
 为什么呢,这跟垃圾收集器方式有关,采用标记回收法,内存清理以后,只是把没用的对象清理出去,空出内存,但是并没有合并整理,导致内存块错综复杂,存活的对象都是错乱的堆放,因此我们需要维护一个列表来记录更新列表上的记录,就好像物流仓库来货了,哪个货架上可以摆放,空余空间多大,仓库管理员在电脑里一查,然后告诉你,去哪儿存放,货要取走,也要告诉管理员,进行记录。这学术之上被称为“空闲列表”。
 还有一种是垃圾收集器采用了标记整理,清理完内存以后,将存活对象的内存块整理摆放,指针指向空余内存块,内存块整齐划一,我们只需要通过移动指针,提供所需的内存给对象就可以了,管理员经常整理货仓,把货物都往前整理,空出后面的内存给后面的货物,我们叫做“指针碰撞”。
 在后续介绍中会给大家介绍垃圾收集器的几种方式,在这里我们先把篇幅放在对象上。
 划分完空间以后,就要把对象的指针指向那片内存,因为创建对象在虚拟机中是一件非常频繁的行为,也就是高并发行为,那么就容易出错,A对象刚刚分配好内存,指针还没来得及修改,B就过来了,使用了原来的指针来分配内存造成错误,解决方法可以对分配空间的动作进行同步处理,虚拟机上采用的CAS加上失败重试,来确保原子性,另一种是给每个线程都分配一块内存,在哪个线程中创建的对象,就在其分配的内存中给对象分配内存。
 分配完内存以后,在对象头中还要设置一下信息,类的元数据信息,对象的哈希码,对象的GC分带年龄等信息,然后执行对象的init()方法,根据程序的要求对对象进行初始化,一个真正可用的对象才算生产出来了,所以,这些过程我们是否也需要了解一下呢,从这部分的基础里,我们可以发现,还需要融合很多其他的知识来完整的理解这个过程,如果你暂时不清楚们一定要记下来,然后查清楚,因为这才是累积,遇到问题,你躲过去,下去还是躲。就从今天开始,摒弃这样的习惯。


2.对象的内存布局
 对象的存储可以分为三个区域:对象头;实例数据;对齐填充。
 对象头:包含两部分信息,第一部分存储对象自身的运行时数据,如哈希码(HashCode)、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳;另一部分是类型指针,指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个实例。
 实例数据:存储对象真正信息的场所。
 对齐补充:占位符,对象数据必须对齐。


3.对象的访问
 我们是操作reference数据开操作对象的,虚拟机如何来操作这个reference是不一定的,目前主流的方式就是使用句柄和直接指针两种方式。
 使用句柄访问的话,需要划出一个句柄池内存,reference指向了句柄池的地址,通过句柄可以指向对象实例数据和类型数据的具体地址。

图片说明

 直接指针的话,reference指向对象地址,而在对象中就要保留类型数据的相关信息。
图片说明
 句柄池相对来说,需要额外维护一块内存给句柄池,但是,句柄池的好处就是稳定,在垃圾回收以后,对象的地址都会变动,此时只需要改变句柄中的实例数据指针,而不要去管reference。
 直接指针,访问速度快,节省了内存空间,但是对象访问频繁的情况的下,优势也并不明显。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章