对象的创建、布局、定位

一、对象的创建
1、检查类
检查对应的类是否已被加载、解析、初始化,如果没有,那么先加载类

2、分配内存,即从堆上面划分一块内存
根据堆内存是否规整,有2种分配方法,堆内存是否规整取决于GC
①、指针碰撞,所有用过的内存在一边,空闲内存在另一边,中间通过指针作为分界点。分配内存时,将指针向空闲区域移动对象大小的距离即可
②、空闲列表,用过的内存与空闲的内存交错,通过列表记录可用内存块。分配内存时,在列表中找到足够大的内存块分配给对象,并更新列表

在分配内存时,另一个需要考虑的是并发冲突,解决方法
①、进行同步处理
②、使用TLAB,本地线程分配缓冲,即在线程的TLAB上分配。当TLAB用完时,会分配新的TLAB,这一步需要同步处理

3、初始化零值
将分配的内存空间除对象头以外都设为0,如果使用TLAB,那么在分配TLAB时就可以初始化零值

4、设置对象头
设置一些信息,如对象是哪个类的实例、如何找到类的元数据、对象的哈希码、对象的GC年代

5、init
执行init方法,按照程序员的意愿初始化,即给字段赋值

二、对象的内存布局
分为3个部分:对象头、实例数据、对齐填充
1、对象头
①、对象运行时数据,如哈希码、GC年代、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
②、类型指针,即指向对象类元数据的指针,并不是所有虚拟机都保留类型指针

2、实例数据
对象的字段内容,包括从父类和子类的数据,存储顺序受虚拟机分配策略及定义顺序的影响

3、对齐填充
占位符,因为对象的起始地址必须是8B的整数倍,所以对象的大小必须是8B的整数倍

三、对象的访问定位
通过栈上面的reference数据来操作堆上面的具体对象主要有2种方式:
1.句柄
在java堆上面划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的地址。reference中存储的是稳定的句柄地址,当对象被移动时,只会改变句柄中实例数据指针

2.直接指针
reference中存储的就是对象地址,在对象的布局中需要考虑如何放置类型数据相关信息。相比于句柄的方式,直接指针节省了一次指针定位的开销

四、 补充
1、对象分配内存时的并发问题
不同线程将同一块内存分配给各自对象,就会产生冲突。通过同步处理,分配内存的过程不能被打断,就避免了冲突。通过TLAB,线程只能在各自对应的TLAB上分配内存,那么不同线程分配内存的区域不同,也就避免了同时将一块内存进行分
2、对象头是否有指向类元数据的指针
当使用直接指针时,需要设置该指针。当使用句柄时,对象头中不需要设置该指针,因为该指针放在句柄中

猜你喜欢

转载自blog.csdn.net/qq_43581718/article/details/107284950