对象的创建
HotSpot虚拟机中,当我们使用new关键字创建一个对象的时候,虚拟机会做如下的操作:
- 查看方法区中的常量池是否能定位到对象类的符号引用,并且检查这个引用是否被加载过、解析和初始化过,如果没有,将先执行类加载;
- 为对象分配内存空间;
- 设置对象头;
- 执行数据初始化;
为对象分配内存空间可能有两种方式:
- 指针碰撞(Bump the pointer):当内存是绝对规整时,按内存的连续性分配内存,以一个指针标记当前已用内存和空闲内存的边界
- 空闲列表(Free List):当内存不规整时,虚拟机需要维护一张列表,记录哪些内存可用
内存是否规整取决于所采用的垃圾回收器,在内存分配时还需要考虑线程安全问题,解决方案有:
- 对分配内存的动作进行同步处理;
- 把内存分配的动作按照线程划分成不同的空间中进行,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),可以用参数-XX:+UseTLAB启用
对象的内存布局
- 对象头(Header):分为两部分,第一部分用于存储对象运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方叫做Mark Word;另一部分是类型指针,即对象指向它的类型数据的指针;另外如果是一个数组对象,对象头中还必须有数组的长度;
- 实例数据(Instance Data)
- 对齐填充(Padding)
对象的访问定位
目前主流的访问方式有使用句柄和直接指针:
- 使用句柄:将在Java堆中划分出一块内存来作为句柄池,引用中存放的就是句柄的位置,句柄中存储了对象的类型指针和对象地址的指针;优势:当垃圾回收时,对象可能被移动,自动后只需要修改句柄中的地址
- 直接指针:引用中直接放的对象的地址,对象的类型通过对象头信息找到;优势:速度更快,节约了一次指针定位的时间