jvm随笔2-新建对象的过程

1 对象创建

在执行new以后会在常量池中定位到一个类的符号引用(检测是否存在),并检测是否已经被加载,解析,初始化过,若没有就会执行类加载。

分配内存:类加载后可确定对象需要的内存,这是就会为新生对象分配内存,方法有两种。

  • 理想状态:假设堆完全规整,可用区域连续,用一个指针划分可用与不可用区域,分配时,移动指针来划分即可。称为“指针碰撞”。
  • 实际状态:并不规整,进行分页,并维护一个列表来记录每个页的使用,分配时划分足够的页,并更新列表即可,称为“空闲列表”。

并发考虑:在分配内存时,需要考虑并发的问题,在为对象A分配内存时,对象B又分配同样的区域就出现了并发问题。

  • 方法一:在分配内存时进行同步操作,保证分配内存的原子性,牺牲时间效率。
  • 方法二:为线程分配私有空间,为对象分配内存时,在线程对应的私有空间中分配,若私有空间不足时,动态拓展私有空间需要进行同步操作,牺牲部分时间效率,部分空间效率。

初始化:分配内存后,将内存空间的初值全部设为0,将对象的信息(那个类的实例,如何寻找类的元数据信息,对象的哈希码,GC分代年龄等)保存在对象头中(类似于消息头),这时候一个对象就产生了,类似一个婴儿的诞生。
所有的字段都为0,这还是一个空白,接着执行init方法,变成真正的对象(从婴儿到成人)。

2.对象内存分配

对象主要有三部分,对象头,实例数据,对象填充。

对象头:对象头中有两部分

  • 第一部分存储对象自身运行时的数据,像哈希码,GC分代年龄,锁状态标志等,这部分是一个不固定的数据结构,根据标志标识对象的状态不同而使这一块区域存储的内容不同。
  • 第二部分是对象的类型指针,指向它的类元数据,标识对象是那个类的实例(也不是所有虚拟机实现都有类型指针,也就是说,查找对象的元数据信息不一定要经过对象本身),若是java数组,还需要记录数组长度,这样才能结合单个数组单元的大小算出整个数组的大小。

实例数据:接下来就是存放的有效信息,一般分配策略都是将相同宽度的字段分配到一起,父类中定义变量出现之类之前,若运行的情况下(参数值CompactFields为true),那么允许子类中较窄的变量插入到父类变量的空隙中。

3.对象访问

需要访问的对象信息有两种,一是对象实例数据,在java堆中,另一个是对象类型数据,在方法区中。

而访问方式目前有两种。一是利用句柄访问,二是使用直接指针

  • 句柄:在java堆中开辟一个空间作为句柄池,存放所有对象的句柄,在栈中有一个reference指向该对象对应的句柄,而句柄的结构是两个指针,一个指向java堆中的对象实例数据,另一个指向方法区中的对象类型数据。
  • 直接指针:使用指针直接指向java堆中的一块区域,这块区域包含指向方法区中对象类型数据的指针和对象实例数据。

对比:使用句柄存储更稳定,在对象被移动时,只需要改变句柄中的指针,不需要改reference。而使用直接指针会减少一次访存的时间,由于对象访问较频繁,所以时间开销节省非常可观。

猜你喜欢

转载自blog.csdn.net/maniacxx/article/details/86546675