Object Instance
对象实例化步骤
- 判断对应的类是否已加载
- (1) 当 new一个对象时, 首先检查 new指令的参数(如
new Demo()的类
)能否在常量池中定位到一个类的符号引用(即判断类元信息是否存在) - (2) 如果没有, 则按双亲委派机制, 使用当前类加载器加载对应的 .class文件, 并生成对应的 Class类对象. 此时如果找不到对应的 .class文件, 则抛出ClassNotFoundException异常
- 为对象分配内存
-
首先计算对象将要占用的空间大小, 然后在堆中分配内存给新对象
* 对象是按成员属性类型和个数来分配空间的(如 当成员属性是引用类型, 则占用32位, 也就是分配4个字节)
-
内存分配有两种方式:
-
(1) 指针碰撞(Bump The Pointer): 当一个内存区域的垃圾收集器, 所采用的清楚阶段算法是
标记压缩(Mark-Compact, 标记整理)算法
, 此区域的内存是属规整的, 因为每当垃圾回收时, 都会将标记过的存活对象依次整理, 将其可用的内存地址具有连续性, 以此解决了内存碎片问题.所谓整理的过程就是指针移动的过程, 又称指针碰撞
-
支持标记压缩算法的回收器. 如 串型回收器(Serial), 并行回收器(ParNew)等
-
(2) 空闲列表(Free List): 当一个内存区域的垃圾收集器, 所采用的清楚阶段算法是
标记清楚(Mark-Sweep)算法
, 此区域的内存是属不规整的, 因为每当回收未被标记的对象后内存地址不做整理(此处不同于标记压缩算法), 由此导致可用的内存地址不连续, 因此产生了很多内存碎片. 所以需要额外维护一个空闲列表来记录这些可用的内存地址, 以备分配新的对象时使用 -
支持标记清楚算法的回收器. 如 并发回收器(CMS)
- 也就是选择哪种分配方式是由所采用的垃圾收集器决定
- 处理并发安全问题
- 由于堆空间是线程共享的区域, 所以有线程安全问题. 因此每当创建对象时会先尝试分配 TLAB缓冲区(Jdk8开始默认开启), 如果未分配到 TLAB, 就会采用 CAS(Compare and Swap, 比较再交换: 一种[乐观锁/无锁算法])失败重试机制, 加锁来保证数据的原子性
- 成员属性初始化
- 按属性类型给每个属性赋予默认值
- 设置对象头
- 将对象的所属类(即类的元信息), 对象的 HashCode, GC信息, 锁信息等数据存储在对象头中
- 执行 init方法进行初始化
- (1) 显式的初始化和代码块中的初始化, 这两种赋值顺序是与 Java代码顺序相关
- (2) 通过构造器初始化
对象的内存布局
- 对象头(Object Header)
- (1) 运行时元数据(Mark Word):
- 哈希值(HashCode)
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
- (2) 类型指针:
- 即对象指向的类的元数据指针(InstanceKlass), 虚拟机通过这个指针确定这个对象属哪个类的实例. (可以通过Object的 getClass方法能知道这个对象是通过哪个类所创建的, 但并不是所有的对象都会保留这个类型指针)
* 如果是数组, 还需记录数组的长度(数组也是对象)
- 实例数据(Instance Data)
- 程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段, 父类中定义的变量总会在子类之前)
- 对齐填充(Padding)
- 不是必须的, 也没特别含义, 仅仅起到占位符的作用
对象内存布局代码实例
/** 对象属性赋值过程:
* 1. 属性默认值初始化
* 2. 显式的初始化/代码块中初始化
* 3. 构造器中初始化
* */
public class Member {
int id; # 属性默认值初始化
int age = 35; # 显式的初始化
String name;
{
name = "全大爷"; # 代码块中初始化
}
Wallet wal;
public Member() {
wal = new Wallet(); # 构造器中初始化
}
}
class Wallet {}
public class UserApp {
public static void main(String[] args) {
Member mem = new Member();
}
}
- 对象内存布局图示
对象的访问定位
- 使用对象时, 通过栈上的 reference数据来操作堆上的具体对象, 有两种方式:
- (1) 句柄访问对象
图 句柄访问对象.png
- (2) 直接指针访问对象(Hotspot虚拟机采用的方式)
* 句柄访问对象相对直接指针访问对象优点为, 当对象被移动时, 无需改动栈上的 reference, 而改动堆上的指定句柄里的对象实例数据指针即可, 但需要额外维护句柄池. 所以性能上总的来讲直接指针访问对象更优一些
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!