Java对象的创建过程
语言层面上,创建对象就是new关键字;
虚拟机中创建对象(不包括数组和Class对象)的过程:
虚拟机遇到一条new指令,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析、初始化。如果没有则执行相应的类加载过程。
在类加载检查通过后,虚拟机将为新生的对象分配内存。对象所需的内存大小在类加载完成后即可确定,为对象分配空间的任务等同于将一块确定大小的内存从java堆中划分出来。
分配方式:指针碰撞和空闲列表。
并发情况下保证线程安全的解决方案:
1.对分配内存空间的动作进行同步处理(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性)
2.把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定
内存分配完成后,虚拟机将分配到的内存空间都初始化为0值(不包括对象头),如果使用TLAB,这一工作可以提前至TLAB分配时进行。
接下来,虚拟机对对象进行必要的设置,例如对象是哪个类的实例,对象的哈希码、对象的GC分代年龄等,这些信息存放在对象头。
以上工作完成后,在虚拟机看来,新的对象已经产生。但是从程序的视角,对象的创建才刚刚开始--<init>方法还没有执行,所有的字段都还为0。new 指令后接着执行init方法,把对象进行初始化,一个真正可用的对象就完全产生出来。
对象的内存分布
对象在内存中存储的布局分为3块区域:对象头、实例数据、对齐填充。
对象头包括:
1.存储对象自身运行时的数据,如哈希码、GC分代年龄、锁状态标志、偏向线程ID、偏向线程锁等,32位或64位虚拟机中分别为32位64位,官方称MarkWord。
2.类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例(并不是所有的都在对象数据上保留类型指针)。如果对象是一个数组,对象头中还必须有一块用于记录数组长度的数据。虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是无法从数组元素的元数据中确定数组的大小。
实例数据是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。、
对齐填充仅仅起着站位的作用。
对象的访问定位
建立对象是为了访问对象,java程序需要通过栈上的reference数据来操作堆上的具体对象。
访问对象的方式有使用句柄和直接访问两种方式。
句柄访问优点:reference中存的是稳定的句柄地址,在对象被移动的时候只会改变句柄中的实例数据指针。
直接访问的优点:速度更快。