对象的创建
new类名—>
根据new的参数在常量池中定位一个类的符号引用—>
如果没有找到符号引用,说明类还没有被加载,则进行类的加载,解析和初始化—>
虚拟机为对象分配内存(位于堆中)—>
将分配的内存初始化为零值(不包括对象头)—>
调用对象的<init>方法
给对象分配内存
堆内存是不连续的,假设堆内存连续的,有一个类似于指针的东西,用于分割使用和未使用的内存区域,当new一个对象的时候指针向未使用的内存方法移动一部分,移动的大小(下图阴影区域)就是创建该对象所使用的内存大小,这种方式称为指针碰撞方式。
当堆内存是不连续的时候,可以采用一张表记录所有空闲的内存区域,当使用后再从表中将被使用的内存区域从表中删除,这种方式称为空闲列表的方式。
线程安全性问题
指针碰撞方式,当多个对象同时new的时候,指针的移动容易造成线程不安全,
解决方式:
1、线程同步
2、本地线程分配缓冲(TLAB)——即将内存区域划分成多个子区域,采用多个线程分别对多个子区域进行操作。(如图)
空闲列表方式也可能会造成线程不安全,也可以采用线程同步进行解决。
对象的结构
Header(对象头:对象的元数据)
对象头主要存储的东西:
自身运行时数据:哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
类型指针:对象指向类的元数据的指针,并非一定需要
InstanceData:实例数据
Padding:自动填充,字节位数不满进行填充
对象的访问定位
1、使用句柄
如果使用句柄访问方式,Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。使用句柄方式最大的好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
2、直接指针
如果使用该方式,Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。使用直接指针方式最大的好处就是速度更快,他节省了一次指针定位的时间开销。