JVM理论:(一/1)对象的创建过程

一、对象的创建

1、当虚拟机遇到一条new指令时,会先检查常量池中能否定位到这个类的符号引用,然后检查这个符号引用代表的类是否被加载、解析、初始化过。

2、如果类还未加载过,要先执行类加载过程,静态变量此时会有一个系统初始值。

3、类加载检查通过后,为新生对象在堆中分配内存,对象所需内存大小在类加载完后可完全确定。

为对象分配内存有两种方法:

  指针碰撞 —— 如果java堆内存绝对规整,用过的内存在一边,空闲的内存都在另一边,中间放一个指针作为分界点,分配时把指针往空闲空间一端移动与对象大小相等的距离。(Serial、ParNew等带Mark-Compact标记整理

  空闲列表 —— java堆不规整,虚拟机维护一个列表,记录哪些内存块可用,分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表记录。(CMS基于Mark-Sweep——标记清除

为对象分配内存时要注意线程安全问题:

   解决对象创建时线程不安全的问题有两种方案,一种是对分配内存空间的动作进行同步处理——CAS配上失败重试;

  一种是使用TLAB让每个线程在java堆中先预留一小块内存-XX:+/-UseTLAB参数控制是否开启

4、分配完空间后,为分配的内存空间初始化零值(不包括对象头),保证对象的实例字段在java代码中可以不附初始值直接使用。这个阶段是虚拟机对对象的初始化,成员变量此时会有一个系统初始值。

5、对象头设置

  对象头存储“对象是哪个类的实例,如何才能找到类的元数据信息,对象哈希码、对象GC分代年龄等,是否使用偏向锁”等信息。

6、执行<init>方法,这个阶段才把对象按程序员的意愿进行初始化。

二、对象的内存布局

  对象是存在堆中的,那么对象的结构又是如何的呢?对象在内存中存储的布局可分为3部分:对象头、实例数据、对齐填充。

1、对象头

(1)对象头第一部分是“Mark Word”,以复用自身存储空间的方式存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

  结构如下图,以第一行为例,在32位的HotSpot虚拟机中,如果对象是无锁的状态,那么这时“Mark Word”的32bit空间中的25bit就用于存对象的哈希码,4bit用于存对象分代年龄,2bit用于存锁标志位,1bit固定为0。

  

(2)对象头的另一部分是类型指针,能通过这个指针来确定该对象是哪个类的实例,即指向方法区中类数据的指针。但不是所有虚拟机都将类型指针保留在对象数据上,结合句柄、直接指针两种方式。

(3)如果对象是数组,对象头还要有一块记录数组长度的数据。

 2、实例数据

   对象真正存储的有效信息,包括存从父类继承下来或在子类中定义的各种类型的字段内容,这部分存储顺序会受虚拟机分配策略参数(FieldsAllocationStyle)和字段在源码中定义顺序的影响。

3、对齐填充部分

  起占位符的作用,保证对象的大小必须是8字节的整数倍。

三、对象的访问

  建立对象后,需要通过栈上的reference数据去定位堆中对象的具体位置,有句柄和直接指针两种方式。

(1)句柄:若使用句柄方式,java堆中会划分出一块内存作为句柄池,reference存的是对象的句柄地址,句柄存的是实例数据与类型数据的地址。

    

(2)直接指针:若使用这种方式reference中存的直接就是对象地址,并且对象的结构里就要考虑如何放置类型指针的相关信息。

   

对比来说,句柄的好处是,reference中存储的是稳定的句柄地址,对象被移动时只会改变句柄中的实例数据指针,reference本身不需要修改。

       直接指针的好处是,速度快,节省了一次指针定位的时间开销,HotSpot采用的是直接指针的方式。

posted @ 2018-07-29 14:07  湮天霸神666  阅读(237)  评论(0编辑  收藏  举报