接下来说下堆中对象的创建
对于Java中的对象创建,常见的有new,还有着 反射 Object.clone 反序列化 Unsafe.allocateInstance方法来新建
在虚拟机接受到一条new指令的时候,会首先检查这个指令的参数对应的类能在常量池汇中查找到吗
如果没有,先进行类加载,
类加载通过,或者验证成功了,为这个新生的对象分配内存空间,所需的内存大小在类加载完成后就可以确定出来,就是在堆空间中确定划分一个确定的区域.
这就不得不说下内存的两种分配方式了
如果堆中的内存是绝对规整的,那么已经使用内存的分配就是将整个指针移动一段和对象相等的距离
这种称为指针碰撞
但如果不是绝对规整的,那就需要维护一个列表,确定列表上哪些内存碎片满足对象分配的大小
这种称为空闲列表
选择哪种分配方式由Java堆是否规整决定,是否规整又由收集器是否携带者压缩整理所决定
Serial ParNew 由Compact过程则是指针碰撞
CMS基于Mark-Sweep则是空闲队列
分配完成内存后,就需要考虑对象在内存中创建问题,对象在内存中创建需要考虑到分配时候的指针安全问题,如果在并发的情况下进行指针分配,那么可能会因为线程切换的问题,导致对象AB之间产生内存分配上的冲突
故解决方案有两种:
一.在分配空间的动作上,进行一个同步,实际上虚拟采用CAS+失败重试的机制保证了操作的原子性
二.将内存的分配按照不同线程分为了多个空间下进行分配,也就是线程挂着一小块的内存空间,称为本地线程分配缓冲(TLAB)
只要TLAB没用完就不用分配新的,用完了再重新分配,在分配的动作上进行同步锁定
现在看来,TLAB是个不错的选择
接下来就是正式的创建一个对象的实例了
对于一个对象的结构,可以分为
对象头 实例对象 对齐填充
对象头中分别存储着两种数据,为自身运行时的实例数据,如哈希码值,GC分代年龄,锁状态标志,线程持有的锁,偏向线程Id等,
而另一部分则是类型指针,说明这个实例是什么类的实例,并非所有的虚拟机实现都需要实现类型指针
实例对象中,存储了对象的真正有效数据,保存了包括从父类中继承下来的多个字段内容
第三部分是对其填充,并非必要存在,但是起着占位符的作用
由于HotSpot VM的自动内存填充要求对象起始地址为8字节的整数倍,所以需要对其填充
还有就是压缩指针,每个Java对象都有一个对象头,由标记字段和类型指针构成
对象头的标记字段占64位,类型指针占了64位,有额外开销16字节,压缩指针就是解决额外开销的问题,利用
强制对其至8个倍数,如果一个对象用不到8N,则也会填充至8N
对于解引用的时候,只需要左移3位就可以了