深入理解java虚拟机之对象的创建

    本文介绍普通java类的new 操作之后的一系列过程;

    对象在new 之后会首先去方法区中找对应的类的引用,并确认该类是否已经完成加载,解析,初始化等操作,如果未执行,则执行类的加载等一系列动作;

    然后是对象的内存的分配,该对象需要分配多大的空间在类加载完成后就已经确定了,实例对象的内存在堆中分配,分配内存实际上就是讲堆中的一块空间划分出来,这个划分的动作有两种实现方式,如果堆空间是整齐的,则已分配的空间与空闲空间之间有一个指针,分配空间时只需要将指针挪动一下位置即可,这种方式成为指针碰撞,如果堆空间不是整齐的,则需要一个空闲列表维护空间的分配,堆空间是否整齐与垃圾回收机制有很大关系;

    值得注意的是,由于堆内存是所有线程共享区域,内存的分配可能会出现线程安全问题;假设线程A分配一块内存,指针还没来得及修改,线程B又在原来的位置移动指针,此时出现了冲突(空闲列表情况与此类似),解决此问题大概有两种方案,一种是对分配内存空间的动作做同步处理,具体操作是以分配失败时重试的方式完成的,另一种是在线程创建时在堆中分配一块该线程特有的内存区域,当该线程创建对象时,只在对应区域中开辟空间,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),只有在TLAB内存不够,需要在堆内存中分配新的TLAB时才会做同步处理,可以通过-XX:+/-UseTLAB参数来设定是否使用TLAB;

    接下来,要讲对象中的成员属性都设置成零值,此时程序访问对象时,也可以使用对象中的字段,其值为对应的0值;然后会对对象的对象头进行设置,信息有HashCode,GC分代年龄等;接着会执行对象的初始化操作;

    对象一般分为3个区域,对象头,实例数据,对齐填充;对象头分为两部分,一部分用于存储对象的运行时数据,HashCode,GC分代年龄,锁状态,线程持有锁,偏向线程ID、偏向时间戳等,另一部分是一个指向对象对应的类的元数据的指针,该部分不是必须的,栈内存也可以根据句柄池的方式找到类的元数据,这要看虚拟机的配置;实例数据是对象存放成员变量的地方,包括了本类的和父类的数据,这部分是对象空间中最长访问的地方;对齐填充没有实际意义,仅仅起占位的作用;

    实际上虚拟机有两种访问的方式,一种是在堆中维护一个句柄池,其保存着对象的地址和对应于方法区的类的原数据的地址,第二种是在对象的头中保存对应的类的元数据的地址;这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,效率更高

深入理解java虚拟机之对象的创建

  深入理解java虚拟机之对象的创建