目录
对象的创建:
对象内存分配的两种方式:
指针碰撞:
空闲列表:
对象的内存布局(基本结构):
对象的访问定位:
主流的访问方式主要有使用句柄和直接指针两种。
对象的创建:
在虚拟机中,当遇到一个new的字节指令的时候,先检查这个字节指令的参数是否能在常量池中定位到一个类的符号引用(可代表这个类),并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
之后JVM给新建对象分配内存,对象所需内存的大小在类加载完成后便可完全确定。为对象分配空间就是把一块确定大小的内存块从Java堆中划分出来。
对象内存分配的两种方式:
指针碰撞:
前提:Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针 作为分界点的指示器
那所分配内存就是把那个指针 向空闲空间方向挪动一段与对象大小相等的距离
空闲列表:
如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起
虚拟机维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
对象的内存布局(基本结构):
对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头部分包括两类信息(或三类)。
- 第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它为“Mark Word”;
- 对象头的另外一部分是类型指针,JVM通过这个指针来确定该对象是哪个类的实例。
- 如果对象是一个Java数组,那在对象头中还有一块用于记录数组长度的数据。
实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
第三部分是对齐填充,仅起着占位符的作用。因为要求对象起始地址必须是8字节的整数倍。所以任何对象的大小都必须是8字节的整数倍。
对象的访问定位:
Student a = new Student(); //这个a到底存了什么东西呢
创建对象自然是为了后续使用该对象,我们的Java程序会通过栈上的reference数据来操作(访问)堆上的具体对象。
主流的访问方式主要有使用句柄和直接指针两种。
句柄访问:Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类数据各自具体的地址信息。(通过reference找句柄,通过句柄找对象实例和类)
直接指针访问:reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销
句柄来访问最大好处:reference中存储的是稳定句柄址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
直接指针访问最大好处:速度更快,它节省了一次指针定位的时间开销。